rack-traffic-signal 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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: []