rack-traffic-signal 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 40eb82af455e2ed57cffd1166baf1b1058cfe0f5
4
+ data.tar.gz: 5cac4ded4b7c77c1ac763e3e917bcb10a7ca79cc
5
+ SHA512:
6
+ metadata.gz: 9fd40d70a8f47adff5c5b99b4284d6e6d4ca106f82146f1ba93f3649da019dc8ff95fb17244a811461576ae20061db1b04d5f42b114d085684f103127e0fe9bb
7
+ data.tar.gz: 05ffe11ec02523b1c9db0a2b8ced2fcb701491c359db7c56a27cc905357bc94a042767ee69f93221897094988473fd86f96e01736c2e598b5bf79fee0ab41833
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.4
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 keigo_yamamoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # Rack::TrafficSignal
2
+
3
+ Rack::TrafficSignal is a Rack middleware to make application in maintenance mode.
4
+ You can make part of application in maintenance mode, and use different http status and response body.
5
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'rack-traffic-signal'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install rack-traffic-signal
22
+
23
+ If you are installing this gem to Rails application, run:
24
+
25
+ $ rails g rack_traffic_signal:install
26
+
27
+ ## Usage
28
+
29
+ ### Rails
30
+
31
+ Generator places initializer template and sample maintenance page.
32
+
33
+ Modify `config/initializers/rack_traffic_signal.rb` .
34
+
35
+ And add below to your `application.rb`
36
+
37
+ ```ruby:application.rb
38
+ require 'rack/traffic_signal'
39
+ ...
40
+ config.middleware.use(Rack::TrafficSignal::Middleware)
41
+ ...
42
+ ```
43
+
44
+ ### Not Rails
45
+
46
+ Insert Rack::TrafficSignal::Middleware to Middleware stack manually, and create setting script.
47
+
48
+ ## Setting
49
+
50
+ ```ruby
51
+ Rack::TrafficSignal.setup do |config|
52
+ # This config is used to internal_access? method.
53
+ # Only add path to this option, mantenance mode is not skipped.
54
+ config.internal_ips = ['192.168.1.1/25']
55
+
56
+ # This config is used to skip_path? method.
57
+ # Only add path to this option, mantenance mode is not skipped.
58
+ config.skip_paths = [/^\/users\/12345/]
59
+
60
+ # Default setting
61
+ config.default_status = 503
62
+ config.default_content_type = 'application/json'
63
+ config.default_body = { error: 'error' }.to_json
64
+
65
+ # Maintenance group settings
66
+ # Maintenance group defined as :<resource>_<action>
67
+
68
+ # If path defined by String, maintenance mode will be applied path is match rigidly.
69
+ # '/users' does not make maintenance mode '/users/foo'.
70
+
71
+ # If multiple setting is appliable, more general setting will be priored.
72
+ # %{/} has higher priority than %{/users/\/d+/foo/bar}
73
+ config.maintenance_group = {
74
+ # <resource>: {
75
+ # <action>: [
76
+ # { methods: [:get, :post], path: <path_to_maintenance>}
77
+ # ]
78
+ # }
79
+ users: {
80
+ register: [
81
+ { methods: [:get], path: "/users/new"},
82
+ { methods: [:post], path: "/users" }
83
+ ],
84
+ update: [
85
+ { methods: [:put], path: %r{/users/\d+}}
86
+ ]
87
+ }
88
+ }
89
+
90
+ # Block to judge whether requested page/api is under maintenance.
91
+ # Returns Array<Symbol>
92
+ # If status array include...
93
+ # :all => all of maintenance groups are enabled.
94
+ # :<resource>_all => maintenance groups under resource
95
+ # ex) :users_all => users_register and users_update are in under maintenance
96
+ # :<resource>_<action> => maintenance group matches is in under maintenance
97
+ config.maintenance_status_by do
98
+ ENV['MAINTENANCE_STATUS'] # [:users_register, :users_update]
99
+ end
100
+
101
+ # Block to judge whether maintenance mode should be skipped.
102
+ # For example, you can skip maintenance mode with specific path or internal access.
103
+ config.skip_by do |env|
104
+ Rack::TrafficSignal.skip_path?(env) || Rack::TrafficSignal.internal_access?
105
+ end
106
+
107
+ # Block to judge whether maintenance mode should be skipped with warning.
108
+ # For example, you can skip maintenance mode with specific path or internal access.
109
+ # Warn by add 'X-RACK-TRAFFIC-SIGNAL-MAINTENANCE' to response header.
110
+ config.skip_with_warning_by do |env|
111
+ Rack::TrafficSignal.skip_path?(env) || Rack::TrafficSignal.internal_access?
112
+ end
113
+ end
114
+ ```
115
+
116
+ ## Contributing
117
+
118
+ 1. Fork it
119
+ 1. Create your feature branch (git checkout -b my-new-feature)
120
+ 1. Commit your changes (git commit -am 'Add some feature')
121
+ 1. Push to the branch (git push origin my-new-feature)
122
+ 1. Create new Pull Request
123
+
124
+ ## License
125
+
126
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
127
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,13 @@
1
+ module RackTrafficSignal
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path('../../templates', __FILE__)
5
+ desc 'Install Rack::TrafficSignal to Rails application'
6
+
7
+ def install
8
+ template 'initializer.rb', 'config/initializers/rack_traffic_signal.rb'
9
+ template '503.html', 'public/503.html'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>This page is under maintenance</title>
5
+ </head>
6
+ <body>
7
+ This page is under maintenance
8
+ </body>
9
+ </html>
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ Rack::TrafficSignal.setup do |config|
4
+ # config.internal_ips = []
5
+ # config.default_status = 503
6
+ # config.default_content_type = 'application/json'
7
+ # require 'json'
8
+ # config.default_body = '{}'.to_json
9
+ # config.maintenance_group = {
10
+ # users: {
11
+ # read: [
12
+ # { methods: [:get], path: '/api/users', status: 404 },
13
+ # { methods: [:get], path: %r{/api/users/\d+}, status: 404, content_type: 'application/json' },
14
+ # ],
15
+ # write: [
16
+ # { methods: [:post], path: '/api/users' body: { error: 'maintenance mode' }.to_json },
17
+ # { methods: [:put, :delete], path: '%r{/api/users/\d+}' }
18
+ # ]
19
+ # },
20
+ # articles: {
21
+ # read: [
22
+ # { methods: [:get], path: '/api/articles' },
23
+ # { methods: [:get], path: %r{/api/articles/\d+} },
24
+ # ],
25
+ # write: [
26
+ # { methods: [:post], path: '/api/articles' },
27
+ # { methods: [:put, :delete], path: '%r{/api/articles/\d+}' }
28
+ # ]
29
+ # },
30
+ # }
31
+ # config.maintenance_status_by do
32
+ # [:users_all, :articles_write]
33
+ # end
34
+ # config.skip_by do |env|
35
+ # false
36
+ # end
37
+ end
@@ -0,0 +1,82 @@
1
+ require 'ipaddress'
2
+
3
+ module Rack
4
+ module TrafficSignal
5
+ class Config
6
+
7
+ attr_reader :maintenance_group
8
+ attr_accessor :internal_ips,
9
+ :skip_paths,
10
+ :default_status,
11
+ :default_content_type,
12
+ :default_body
13
+
14
+ def initialize
15
+ @internal_ips = []
16
+ @maintenance_group = { }
17
+ @default_status = 503
18
+ @default_content_type = 'application/json'
19
+ @default_body = ''
20
+ @skip_paths = [/^\/assets/]
21
+ @maintenance_status_proc = ->{ [] }
22
+ @skip_proc = ->(env){ false }
23
+ @skip_with_warning_proc = ->(env){ false }
24
+ end
25
+
26
+ def maintenance_group=(mg)
27
+ raise Rack::TrafficSignal::Exceptions::InvalidMaintenanceGroup unless valid_maintenance_group?(mg)
28
+ @maintenance_group = mg
29
+ end
30
+
31
+ def maintenance_status_by(&block)
32
+ @maintenance_status_proc = block
33
+ end
34
+
35
+ def maintenance_status
36
+ @maintenance_status_proc.call.tap do |status|
37
+ raise Rack::TrafficSignal::Exceptions::InvalidMaintenanceStatus unless status.is_a? Array
38
+ raise Rack::TrafficSignal::Exceptions::InvalidMaintenanceStatus unless status.all? do |state|
39
+ state.is_a?(Symbol) && (state == :all || state.to_s =~ /\A[A-Za-z0-9]+_[A-Za-z0-9]+\z/)
40
+ end
41
+ end
42
+ end
43
+
44
+ def skip_by(&block)
45
+ @skip_proc = block
46
+ end
47
+
48
+ def skip?(env)
49
+ @skip_proc.call(env)
50
+ end
51
+
52
+ def skip_with_warning_by(&block)
53
+ @skip_with_warning_proc = block
54
+ end
55
+
56
+ def skip_with_warning?(env)
57
+ @skip_with_warning_proc.call(env)
58
+ end
59
+
60
+ private
61
+ def valid_maintenance_group?(mg)
62
+ return false unless mg.is_a? Hash
63
+
64
+ mg.values.each do |group|
65
+ return false unless group.is_a? Hash
66
+ group.values.each do |igroup|
67
+ return false unless igroup.is_a? Array
68
+ igroup.each do |setting|
69
+ return false unless setting.key?(:methods)
70
+ return false unless setting.key? :path
71
+ end
72
+ end
73
+ end
74
+
75
+ true
76
+
77
+ rescue
78
+ false
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,8 @@
1
+ module Rack
2
+ module TrafficSignal
3
+ module Exceptions
4
+ class InvalidMaintenanceGroup < StandardError; end
5
+ class InvalidMaintenanceStatus < StandardError; end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,100 @@
1
+ require 'json'
2
+
3
+ module Rack
4
+ module TrafficSignal
5
+ class Middleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ return @app.call(env) if config.skip?(env)
12
+ method = env['REQUEST_METHOD'].downcase.to_sym
13
+ path = env['PATH_INFO']
14
+ path = path.chop if path != '/' && path[-1] == '/'
15
+
16
+ applied_config = maintenance_application(method, path)
17
+
18
+ if applied_config
19
+ if config.skip_with_warning?(env)
20
+ @app.call(env).tap do |status, headers, body|
21
+ headers['X-RACK-TRAFFIC-SIGNAL-MAINTENENCE'] = '1'
22
+ end
23
+ else
24
+ build_response(applied_config)
25
+ end
26
+ else
27
+ @app.call(env)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def build_response(applied_config)
34
+ status = applied_config[:status] ? applied_config[:status] : config.default_status
35
+ content_type = applied_config[:content_type] ? applied_config[:content_type] : config.default_content_type
36
+ body = applied_config[:body] ? applied_config[:body] : config.default_body
37
+ header = { "Content-Type" => content_type, 'Content-Length' => body.bytesize.to_s }
38
+
39
+ [status, header, [body]]
40
+ end
41
+
42
+ def config_appliable?(config, method, path)
43
+ path_match = if config[:path].is_a?(String)
44
+ path == config[:path]
45
+ elsif config[:path].is_a? Regexp
46
+ path.match(config[:path])
47
+ else
48
+ false
49
+ end
50
+
51
+ path_match && config[:methods].include?(method)
52
+ end
53
+
54
+ def path_length(path)
55
+ if path.is_a? String
56
+ path.split('/').length
57
+ elsif path.is_a? Regexp
58
+ path.source.split('/').length
59
+ end
60
+ end
61
+
62
+ def maintenance_application(method, path)
63
+ enabled_maintenance_mode = config.maintenance_status
64
+
65
+ enabled_cfg = if enabled_maintenance_mode.include? :all
66
+ config.maintenance_group.values.inject([]) do |a, p|
67
+ a + p.values
68
+ end
69
+ else
70
+ enabled_maintenance_mode.inject([]) do |a, mode|
71
+ resource, action = *mode.to_s.split('_').map(&:to_sym)
72
+ if action == :all
73
+ a + config.maintenance_group[resource].values
74
+ else
75
+ a + config.maintenance_group[resource][action]
76
+ end
77
+ end
78
+ end
79
+
80
+ enabled_cfg.flatten!
81
+
82
+ applied = enabled_cfg
83
+ .uniq
84
+ .select { |c| config_appliable?(c, method, path) }
85
+ .sort { |a, b| path_length(a[:path]) <=> path_length(b[:path]) }
86
+ if applied.length > 0
87
+ return applied[0]
88
+ else
89
+ return nil
90
+ end
91
+ rescue NoMethodError
92
+ raise Rack::TrafficSignal::Exceptions::InvalidMaintenanceGroup
93
+ end
94
+
95
+ def config
96
+ Rack::TrafficSignal.config
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,9 @@
1
+ module Rack
2
+ module TrafficSignal
3
+ class Railtie < ::Rails::Railtie
4
+ initializer 'insert_middleware' do |app|
5
+ app.config.middleware.use Rack::TrafficSignal::Middleware
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module TrafficSignal
3
+ VERSION = "0.1.2"
4
+ end
5
+ end
@@ -0,0 +1,44 @@
1
+ require 'rack/traffic_signal/version'
2
+ require 'rack/traffic_signal/config'
3
+ require 'rack/traffic_signal/exceptions'
4
+ require 'rack/traffic_signal/middleware'
5
+ require 'rack/traffic_signal/railtie' if defined? Rails
6
+
7
+ module Rack
8
+ module TrafficSignal
9
+ def self.setup
10
+ yield(config)
11
+ end
12
+
13
+ def self.config
14
+ @config ||= Config.new
15
+ end
16
+
17
+ def self.internal_access?(env)
18
+ remote_ip = IPAddress(request_from(env))
19
+ config.internal_ips
20
+ .map { |internal_ip| IPAddress(internal_ip) }
21
+ .any? { |internal_ip| internal_ip.include?(remote_ip) if remote_ip.class == internal_ip.class }
22
+ end
23
+
24
+ def self.skip_path?(env)
25
+ path = env['PATH_INFO']
26
+ path = path.chop if path[-1] == '/'
27
+ config.skip_paths.any? do |skip_path|
28
+ if skip_path.is_a? Regexp
29
+ path.match(skip_path)
30
+ elsif skip_path.is_a? String
31
+ path == skip_path
32
+ else
33
+ false
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+ def self.request_from(env)
40
+ return env['REMOTE_ADDR'] unless env['HTTP_X_FORWARDED_FOR']
41
+ env['HTTP_X_FORWARDED_FOR'].split(/,/)[0].strip
42
+ end
43
+ end
44
+ end
@@ -0,0 +1 @@
1
+ require 'rack/traffic_signal'
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack/traffic_signal/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rack-traffic-signal"
8
+ spec.version = Rack::TrafficSignal::VERSION
9
+ spec.authors = ["keigo yamamoto"]
10
+ spec.email = ["k5.trismegistus@gmail.com"]
11
+
12
+ spec.summary = 'Traffic signal of rack application'
13
+ spec.description = 'Make your app maintenance mode. You can prohibit all/a part of external access'
14
+ spec.homepage = "https://github.com/k5trismegistus/rack-traffic-signal"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "ipaddress"
23
+ spec.add_development_dependency "bundler", "~> 1.14"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "pry"
27
+ spec.add_development_dependency "rack-test"
28
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-traffic-signal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - keigo yamamoto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ipaddress
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Make your app maintenance mode. You can prohibit all/a part of external
98
+ access
99
+ email:
100
+ - k5.trismegistus@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - lib/generators/rack_traffic_signal/install_generator.rb
113
+ - lib/generators/templates/503.html
114
+ - lib/generators/templates/initializer.rb
115
+ - lib/rack-traffic_signal.rb
116
+ - lib/rack/traffic_signal.rb
117
+ - lib/rack/traffic_signal/config.rb
118
+ - lib/rack/traffic_signal/exceptions.rb
119
+ - lib/rack/traffic_signal/middleware.rb
120
+ - lib/rack/traffic_signal/railtie.rb
121
+ - lib/rack/traffic_signal/version.rb
122
+ - rack-traffic-signal.gemspec
123
+ homepage: https://github.com/k5trismegistus/rack-traffic-signal
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.4.5.1
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Traffic signal of rack application
147
+ test_files: []