unicorn-cuba-base 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +20 -0
- data/README.md +29 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/unicorn-cuba-base.rb +161 -0
- data/lib/unicorn-cuba-base/memory_limit.rb +62 -0
- data/lib/unicorn-cuba-base/plugin/error_matcher.rb +12 -0
- data/lib/unicorn-cuba-base/plugin/logging.rb +9 -0
- data/lib/unicorn-cuba-base/plugin/memory_limit.rb +8 -0
- data/lib/unicorn-cuba-base/plugin/response_helpers.rb +83 -0
- data/lib/unicorn-cuba-base/rack/error_handling.rb +31 -0
- data/lib/unicorn-cuba-base/rack/memory_limit.rb +20 -0
- data/lib/unicorn-cuba-base/rack/unhandled_request.rb +22 -0
- data/lib/unicorn-cuba-base/root_logger.rb +110 -0
- data/lib/unicorn-cuba-base/stats.rb +30 -0
- data/lib/unicorn-cuba-base/stats_reporter.rb +23 -0
- data/spec/memory_limit_spec.rb +66 -0
- data/spec/root_logger_spec.rb +58 -0
- data/spec/spec_helper.rb +12 -0
- data/unicorn-cuba-base.gemspec +87 -0
- metadata +219 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
ruby "1.9.3"
|
4
|
+
|
5
|
+
gem "cuba", "~> 3.0"
|
6
|
+
gem "unicorn", ">= 4.6.2"
|
7
|
+
gem "raindrops", "~> 0.11"
|
8
|
+
gem "cli", "~> 1.1.0"
|
9
|
+
gem "facter", "~> 1.6.11"
|
10
|
+
gem "ruby-ip", "~> 0.9"
|
11
|
+
|
12
|
+
# Add dependencies to develop your gem here.
|
13
|
+
# Include everything needed to run rake, tests, features, etc.
|
14
|
+
group :development do
|
15
|
+
gem "rspec", "~> 2.13"
|
16
|
+
gem "rdoc", "~> 3.9"
|
17
|
+
gem "jeweler", "~> 1.8.4"
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
cli (1.1.1)
|
5
|
+
cuba (3.1.0)
|
6
|
+
rack
|
7
|
+
diff-lcs (1.2.4)
|
8
|
+
facter (1.6.18)
|
9
|
+
git (1.2.5)
|
10
|
+
jeweler (1.8.4)
|
11
|
+
bundler (~> 1.0)
|
12
|
+
git (>= 1.2.5)
|
13
|
+
rake
|
14
|
+
rdoc
|
15
|
+
json (1.7.7)
|
16
|
+
kgio (2.8.0)
|
17
|
+
rack (1.5.2)
|
18
|
+
raindrops (0.11.0)
|
19
|
+
rake (10.0.4)
|
20
|
+
rdoc (3.12.2)
|
21
|
+
json (~> 1.4)
|
22
|
+
rspec (2.13.0)
|
23
|
+
rspec-core (~> 2.13.0)
|
24
|
+
rspec-expectations (~> 2.13.0)
|
25
|
+
rspec-mocks (~> 2.13.0)
|
26
|
+
rspec-core (2.13.1)
|
27
|
+
rspec-expectations (2.13.0)
|
28
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
29
|
+
rspec-mocks (2.13.1)
|
30
|
+
ruby-ip (0.9.1)
|
31
|
+
unicorn (4.6.2)
|
32
|
+
kgio (~> 2.6)
|
33
|
+
rack
|
34
|
+
raindrops (~> 0.7)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
cli (~> 1.1.0)
|
41
|
+
cuba (~> 3.0)
|
42
|
+
facter (~> 1.6.11)
|
43
|
+
jeweler (~> 1.8.4)
|
44
|
+
raindrops (~> 0.11)
|
45
|
+
rdoc (~> 3.9)
|
46
|
+
rspec (~> 2.13)
|
47
|
+
ruby-ip (~> 0.9)
|
48
|
+
unicorn (>= 4.6.2)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Jakub Pastuszek
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# unicorn-cuba-base
|
2
|
+
|
3
|
+
It is web application base powered by Unicorn HTTP server and based on Cuba framework extended with additional Rack middleware and Cuba plugins.
|
4
|
+
|
5
|
+
This gem was created to deduplicate code from [httpthumbnailer](http://github.com/jpastuszek/httpthumbnailer) and [httpimagestore](http://github.com/jpastuszek/httpimagestore).
|
6
|
+
|
7
|
+
## Installing
|
8
|
+
|
9
|
+
gem install unicorn-cuba-base
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Please see [httpthumbnailer main executable](https://github.com/jpastuszek/httpthumbnailer/blob/master/bin/httpthumbnailer) and [httpimagestore main executable](https://github.com/jpastuszek/httpimagestore/blob/master/bin/httpimagestore) for usage example.
|
14
|
+
|
15
|
+
## Contributing to unicorn-cuba-base
|
16
|
+
|
17
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
18
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
19
|
+
* Fork the project
|
20
|
+
* Start a feature/bugfix branch
|
21
|
+
* Commit and push until you are happy with your contribution
|
22
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
23
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
24
|
+
|
25
|
+
## Copyright
|
26
|
+
|
27
|
+
Copyright (c) 2013 Jakub Pastuszek. See LICENSE.txt for
|
28
|
+
further details.
|
29
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "unicorn-cuba-base"
|
18
|
+
gem.homepage = "http://github.com/jpastuszek/unicorn-cuba-base"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = "web appliaction base powered by Unicorn and Cuba"
|
21
|
+
gem.description = "web application base powered by Unicorn HTTP server and based on Cuba framework extended with additional Rack middleware and Cuba plugins"
|
22
|
+
gem.email = "jpastuszek@gmail.com"
|
23
|
+
gem.authors = ["Jakub Pastuszek"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
RDoc::Task.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "unicorn-cuba-base #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'cli'
|
2
|
+
require 'cuba'
|
3
|
+
require 'unicorn'
|
4
|
+
require 'unicorn/launcher'
|
5
|
+
require 'facter'
|
6
|
+
require 'pathname'
|
7
|
+
require 'ip'
|
8
|
+
|
9
|
+
require_relative 'unicorn-cuba-base/stats'
|
10
|
+
require_relative 'unicorn-cuba-base/root_logger'
|
11
|
+
require_relative 'unicorn-cuba-base/plugin/error_matcher'
|
12
|
+
require_relative 'unicorn-cuba-base/plugin/logging'
|
13
|
+
require_relative 'unicorn-cuba-base/plugin/response_helpers'
|
14
|
+
require_relative 'unicorn-cuba-base/plugin/memory_limit'
|
15
|
+
require_relative 'unicorn-cuba-base/rack/error_handling'
|
16
|
+
require_relative 'unicorn-cuba-base/rack/unhandled_request'
|
17
|
+
require_relative 'unicorn-cuba-base/rack/memory_limit'
|
18
|
+
|
19
|
+
class Controler < Cuba
|
20
|
+
include ClassLogging
|
21
|
+
end
|
22
|
+
|
23
|
+
require_relative 'unicorn-cuba-base/stats_reporter'
|
24
|
+
|
25
|
+
class Application
|
26
|
+
def cli(&block)
|
27
|
+
@cli_setup = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def settings(&block)
|
31
|
+
@settings_setup = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def main(&block)
|
35
|
+
@main_setup = block
|
36
|
+
end
|
37
|
+
|
38
|
+
def after_fork(&block)
|
39
|
+
@after_fork = block
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(program_name, defaults = {}, &block)
|
43
|
+
instance_eval &block
|
44
|
+
|
45
|
+
@cli = setup_cli(program_name, defaults, @cli_setup) or fail 'no cli defined'
|
46
|
+
@settings = @settings_setup ? setup_settings(@settings_setup) : @cli.parse!
|
47
|
+
|
48
|
+
root_logger = RootLogger.new(STDERR)
|
49
|
+
root_logger.level = RootLogger::WARN
|
50
|
+
root_logger.level = RootLogger::INFO if @settings.verbose
|
51
|
+
root_logger.level = RootLogger::DEBUG if @settings.debug
|
52
|
+
Controler.logger = root_logger
|
53
|
+
MemoryLimit.logger = Controler.logger_for(MemoryLimit)
|
54
|
+
|
55
|
+
unicorn_settings = {}
|
56
|
+
unicorn_settings[:logger] = root_logger.logger_for(Unicorn::HttpServer)
|
57
|
+
unicorn_settings[:pid] = @settings.pid_file.to_s
|
58
|
+
unicorn_settings[:worker_processes] = @settings.worker_processes
|
59
|
+
unicorn_settings[:timeout] = @settings.worker_timeout
|
60
|
+
unicorn_settings[:listeners] = @settings.listener
|
61
|
+
unicorn_settings[:user] = @settings.user if @settings.user
|
62
|
+
unicorn_settings[:rewindable_input] = false # don't keep the upload data in memory or on disk (tmp)
|
63
|
+
unicorn_settings[:after_fork] = @after_fork if @after_fork
|
64
|
+
|
65
|
+
unless @settings.foreground
|
66
|
+
unicorn_settings[:stderr_path] = @settings.log_file.to_s
|
67
|
+
unicorn_settings[:stdout_path] = @settings.log_file.to_s
|
68
|
+
|
69
|
+
Unicorn::Launcher.daemonize!(unicorn_settings)
|
70
|
+
|
71
|
+
# capture startup messages
|
72
|
+
@settings.log_file.open('ab') do |log|
|
73
|
+
log.sync = true
|
74
|
+
STDERR.reopen log
|
75
|
+
STDOUT.reopen log
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Controler.settings[:listeners] = @settings.listener
|
80
|
+
Controler.settings[:access_log_file] = @settings.access_log_file
|
81
|
+
|
82
|
+
Controler.plugin Plugin::ErrorMatcher
|
83
|
+
Controler.plugin Plugin::Logging
|
84
|
+
Controler.plugin Plugin::ResponseHelpers
|
85
|
+
Controler.plugin Plugin::MemoryLimit
|
86
|
+
|
87
|
+
@main_setup or fail 'no main controler provided'
|
88
|
+
main_controler = setup_main(@main_setup) or fail 'no main controler class returned'
|
89
|
+
|
90
|
+
access_log_file = @settings.access_log_file.open('a+')
|
91
|
+
access_log_file.sync = true
|
92
|
+
main_controler.use Rack::CommonLogger, access_log_file
|
93
|
+
main_controler.use Rack::ErrorHandling
|
94
|
+
main_controler.use Rack::UnhandledRequest
|
95
|
+
main_controler.use Rack::MemoryLimit, @settings.limit_memory * 1024 ** 2
|
96
|
+
|
97
|
+
Unicorn::HttpServer.new(main_controler, unicorn_settings).start.join
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def setup_cli(program_name, defaults, block)
|
103
|
+
CLI.new do
|
104
|
+
instance_eval &block
|
105
|
+
option :log_file,
|
106
|
+
short: :l,
|
107
|
+
cast: Pathname,
|
108
|
+
description: 'log file location',
|
109
|
+
default: "#{program_name}.log"
|
110
|
+
option :access_log_file,
|
111
|
+
short: :a,
|
112
|
+
cast: Pathname,
|
113
|
+
description: 'NCSA access log file location',
|
114
|
+
default: "#{program_name}_access.log"
|
115
|
+
option :pid_file,
|
116
|
+
short: :p,
|
117
|
+
cast: Pathname,
|
118
|
+
description: 'PID file location',
|
119
|
+
default: "#{program_name}.pid"
|
120
|
+
switch :foreground,
|
121
|
+
short: :f,
|
122
|
+
description: 'stay in foreground'
|
123
|
+
options :listener,
|
124
|
+
short: :L,
|
125
|
+
description: 'HTTP server listener (bind) address in format <ip>:<port> or unix:<file> or ~[<username>]/<file> for UNIX sockets',
|
126
|
+
default: '127.0.0.1:' + (defaults[:port] || 8080).to_s
|
127
|
+
option :user,
|
128
|
+
short: :u,
|
129
|
+
description: 'run worker processes as given user'
|
130
|
+
option :worker_processes,
|
131
|
+
short: :w,
|
132
|
+
cast: Integer,
|
133
|
+
description: 'start given number of worker processes',
|
134
|
+
default: (Facter.processorcount.to_i + 1) * (defaults[:processor_count_factor] || 1)
|
135
|
+
option :worker_timeout,
|
136
|
+
short: :t,
|
137
|
+
cast: Integer,
|
138
|
+
description: 'workers handling the request taking longer than this time period will be forcibly killed',
|
139
|
+
default: 300
|
140
|
+
option :limit_memory,
|
141
|
+
cast: Integer,
|
142
|
+
description: 'memory usage limit in MiB',
|
143
|
+
default: 128
|
144
|
+
switch :verbose,
|
145
|
+
short: :v,
|
146
|
+
description: 'enable verbose logging (INFO)'
|
147
|
+
switch :debug,
|
148
|
+
short: :d,
|
149
|
+
description: 'enable verbose and debug logging (DEBUG)'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def setup_settings(block)
|
154
|
+
@cli.parse!(&block)
|
155
|
+
end
|
156
|
+
|
157
|
+
def setup_main(block)
|
158
|
+
block.call @settings
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'unicorn-cuba-base/root_logger'
|
2
|
+
|
3
|
+
class MemoryLimit
|
4
|
+
include ClassLogging
|
5
|
+
|
6
|
+
class MemoryLimitedExceededError < RuntimeError
|
7
|
+
def initialize
|
8
|
+
super "memory limit exceeded"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module IO
|
13
|
+
def root_limit(ml)
|
14
|
+
@root_limit = ml
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(bytes = nil)
|
18
|
+
data = @root_limit.get do |max_read_bytes|
|
19
|
+
if not bytes or bytes > max_read_bytes
|
20
|
+
data = super max_read_bytes
|
21
|
+
raise MemoryLimitedExceededError unless eof?
|
22
|
+
data or '' # read() always returns '' on EOF
|
23
|
+
else
|
24
|
+
super bytes or ''
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(bytes = 256 * 1024 ** 2)
|
31
|
+
log.info "using memory limit of #{bytes} bytes" if bytes
|
32
|
+
@limit = bytes
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :limit
|
36
|
+
|
37
|
+
def get
|
38
|
+
yield(@limit).tap do |data|
|
39
|
+
borrow data.bytesize if data
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def borrow(bytes)
|
44
|
+
log.debug "borrowing #{bytes} from #{@limit} bytes of limit"
|
45
|
+
bytes > @limit and raise MemoryLimitedExceededError
|
46
|
+
@limit -= bytes
|
47
|
+
bytes
|
48
|
+
end
|
49
|
+
|
50
|
+
def return(bytes)
|
51
|
+
log.debug "returning #{bytes} to #{@limit} bytes of limit"
|
52
|
+
@limit += bytes
|
53
|
+
bytes
|
54
|
+
end
|
55
|
+
|
56
|
+
def io(io)
|
57
|
+
io.extend MemoryLimit::IO
|
58
|
+
io.root_limit self
|
59
|
+
io
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'unicorn-cuba-base/stats'
|
3
|
+
|
4
|
+
module Plugin
|
5
|
+
module ResponseHelpers
|
6
|
+
extend Stats
|
7
|
+
def_stats(
|
8
|
+
:total_write_multipart,
|
9
|
+
:total_write,
|
10
|
+
:total_write_part,
|
11
|
+
:total_write_error,
|
12
|
+
:total_write_error_part
|
13
|
+
)
|
14
|
+
|
15
|
+
def write(code, content_type, body)
|
16
|
+
req.body.read # read all remaining upload before we send response so that client will read it
|
17
|
+
res.status = code
|
18
|
+
res["Content-Type"] = content_type
|
19
|
+
ResponseHelpers.stats.incr_total_write
|
20
|
+
res.write body
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_plain(code, msg)
|
24
|
+
msg = msg.join("\r\n") if msg.is_a? Array
|
25
|
+
write code, 'text/plain', msg.gsub(/(?<!\r)\n/, "\r\n") + "\r\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_url_list(code, msg)
|
29
|
+
msg = msg.join("\r\n") if msg.is_a? Array
|
30
|
+
write code, 'text/uri-list', msg.gsub(/(?<!\r)\n/, "\r\n") + "\r\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_error(code, error)
|
34
|
+
msg = error.message
|
35
|
+
log.warn "sending #{code} error response: #{msg}"
|
36
|
+
ResponseHelpers.stats.incr_total_write_error
|
37
|
+
write_plain code, msg
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_url_list(code, urls)
|
41
|
+
write code, 'text/uri-list', urls.join("\r\n") + "\r\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Multipart
|
45
|
+
def write_preamble(code, headers = {})
|
46
|
+
res.status = code
|
47
|
+
@boundary = SecureRandom.uuid
|
48
|
+
res["Content-Type"] = "multipart/mixed; boundary=\"#{@boundary}\""
|
49
|
+
headers.each do |key, value|
|
50
|
+
res[key] = value
|
51
|
+
end
|
52
|
+
ResponseHelpers.stats.incr_total_write_multipart
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_part(content_type, body, headers = {})
|
56
|
+
res.write "--#{@boundary}\r\n"
|
57
|
+
res.write "Content-Type: #{content_type}\r\n"
|
58
|
+
headers.each_pair do |name, value|
|
59
|
+
res.write "#{name}: #{value}\r\n"
|
60
|
+
end
|
61
|
+
res.write "\r\n"
|
62
|
+
ResponseHelpers.stats.incr_total_write_part
|
63
|
+
res.write body
|
64
|
+
res.write "\r\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
def write_plain_part(msg, headers = {})
|
68
|
+
write_part 'text/plain', msg.to_s.gsub("\n", "\r\n"), headers
|
69
|
+
end
|
70
|
+
|
71
|
+
def write_error_part(code, error)
|
72
|
+
msg = error.message
|
73
|
+
log.warn "sending error in multipart response part: #{msg}"
|
74
|
+
ResponseHelpers.stats.incr_total_write_error_part
|
75
|
+
write_plain_part msg, 'Status' => code
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_epilogue
|
79
|
+
res.write "--#{@boundary}--\r\n"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class ErrorHandling
|
5
|
+
def initialize(app, &block)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
# save original env
|
11
|
+
orig_env = env.dup
|
12
|
+
begin
|
13
|
+
return @app.call(env)
|
14
|
+
rescue => error
|
15
|
+
begin
|
16
|
+
# reset env to original since it could have been changed
|
17
|
+
env.clear
|
18
|
+
env.merge!(orig_env)
|
19
|
+
|
20
|
+
# set error so app can handle it
|
21
|
+
env["app.error"] = error
|
22
|
+
|
23
|
+
return @app.call(env)
|
24
|
+
rescue
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative '../memory_limit'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MemoryLimit
|
5
|
+
def initialize(app, memory_limit)
|
6
|
+
@app = app
|
7
|
+
@memory_limit = memory_limit
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
memory_limit = ::MemoryLimit.new(@memory_limit)
|
12
|
+
env["app.memory_limit"] = memory_limit
|
13
|
+
|
14
|
+
# use up limit when reading request data
|
15
|
+
memory_limit.io env["rack.input"]
|
16
|
+
return @app.call(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rack
|
2
|
+
class UnhandledRequest
|
3
|
+
class UnhandledRequestError < ArgumentError
|
4
|
+
attr_reader :uri
|
5
|
+
def initialize(uri)
|
6
|
+
@uri = uri
|
7
|
+
super "request for URI '#{uri}' was not handled by the server"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
status, headers, body = @app.call(env)
|
17
|
+
raise UnhandledRequestError, env['SCRIPT_NAME'] + env['PATH_INFO'] if body == [] and (status == 200 or status == 404)
|
18
|
+
[status, headers, body]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class RootLogger < Logger
|
4
|
+
class ClassLogger
|
5
|
+
@@levels = [:debug, :info, :warn, :error, :fatal, :unknown]
|
6
|
+
|
7
|
+
def initialize(root_logger, class_obj)
|
8
|
+
@root_logger = root_logger
|
9
|
+
@progname = class_obj.name
|
10
|
+
@root_logger.formatter = proc do |severity, datetime, progname, msg|
|
11
|
+
"[#{datetime.utc.strftime "%Y-%m-%d %H:%M:%S.%6N %Z"}] [#{$$} #{progname}] #{severity}: #{msg}\n"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to?(method)
|
16
|
+
super or @root_logger.respond_to? method
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(name, *args, &block)
|
20
|
+
if @@levels.include? name
|
21
|
+
message = if block_given?
|
22
|
+
self.progname
|
23
|
+
else
|
24
|
+
args.map do |arg|
|
25
|
+
if arg.is_a? Exception
|
26
|
+
"#{arg.class.name}: #{arg.message}\n#{arg.backtrace.join("\n")}"
|
27
|
+
else
|
28
|
+
arg.to_s
|
29
|
+
end
|
30
|
+
end.join(': ')
|
31
|
+
end
|
32
|
+
|
33
|
+
# set program name to current class
|
34
|
+
@root_logger.progname = @progname
|
35
|
+
@root_logger.send(name, message.chomp, &block)
|
36
|
+
else
|
37
|
+
@root_logger.send(name, *args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :root_logger
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"#<ClassLogger[#{@progname}] #{"0x%X" % object_id} root_logger=#{@root_logger.inspect}>"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def logger_for(class_obj)
|
49
|
+
ClassLogger.new(self, class_obj)
|
50
|
+
end
|
51
|
+
|
52
|
+
def root_logger
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#<RootLogger #{"0x%X" % object_id}>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassLogging
|
62
|
+
module ClassMethods
|
63
|
+
def init_logger
|
64
|
+
@@logger = {} unless defined? @@logger
|
65
|
+
end
|
66
|
+
|
67
|
+
def logger=(logger)
|
68
|
+
@@logger[self] = logger
|
69
|
+
end
|
70
|
+
|
71
|
+
def log
|
72
|
+
unless @@logger[self]
|
73
|
+
new_root_logger = false
|
74
|
+
# use root logger from ancestor or create new one
|
75
|
+
root_logger =
|
76
|
+
if logging_class = ancestors.find{|an| an != self and an.respond_to? :log}
|
77
|
+
logging_class.log.respond_to?(:root_logger) ? logging_class.log.root_logger : logging_class.log
|
78
|
+
else
|
79
|
+
new_root_logger = true
|
80
|
+
Logger.new(STDERR)
|
81
|
+
end
|
82
|
+
|
83
|
+
root_logger.kind_of? RootLogger::ClassLogger and fail "got ClassLogger root logger: #{self}"
|
84
|
+
|
85
|
+
logger = RootLogger::ClassLogger.new(root_logger, self)
|
86
|
+
logger.warn "new default logger crated" if new_root_logger
|
87
|
+
@@logger[self] = logger
|
88
|
+
end
|
89
|
+
@@logger[self]
|
90
|
+
end
|
91
|
+
|
92
|
+
def logger_for(class_obj)
|
93
|
+
RootLogger::ClassLogger.new(log.root_logger, class_obj)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def log
|
98
|
+
self.class.log
|
99
|
+
end
|
100
|
+
|
101
|
+
def logger_for(class_obj)
|
102
|
+
self.class.logger_for(class_obj)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.included(class_obj)
|
106
|
+
class_obj.extend ClassMethods
|
107
|
+
class_obj.init_logger
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'raindrops'
|
2
|
+
|
3
|
+
module Stats
|
4
|
+
class MyStruct < Raindrops::Struct
|
5
|
+
def self.new(*members)
|
6
|
+
klass = super(*members)
|
7
|
+
|
8
|
+
str = ''
|
9
|
+
# add support to increment by more than 1
|
10
|
+
members.map { |x| x.to_sym }.each_with_index do |member, i|
|
11
|
+
str << "def incr_#{member}(v = 1); @raindrops.incr(#{i}, v); end; "
|
12
|
+
str << "def decr_#{member}(v = 1); @raindrops.decr(#{i}, v); end; "
|
13
|
+
end
|
14
|
+
|
15
|
+
klass.class_eval(str)
|
16
|
+
klass
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def def_stats(*stat_names)
|
21
|
+
@@local_stats ||= {}
|
22
|
+
stats_class = eval "MyStruct.new(#{stat_names.map{|s| ":#{s.to_s}"}.join(', ')})"
|
23
|
+
@@local_stats[self] = stats_class.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def stats
|
27
|
+
@@local_stats[self]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class StatsReporter < Controler
|
2
|
+
def self.<<(stats)
|
3
|
+
(@@stats ||= []) << stats
|
4
|
+
end
|
5
|
+
|
6
|
+
self.define do
|
7
|
+
all_stats = {}
|
8
|
+
@@stats.each do |stats|
|
9
|
+
stats.class::MEMBERS.each.with_index.map do |stat, index|
|
10
|
+
all_stats[stat] = stats[index]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
on :stat do |stat|
|
15
|
+
write_plain 200, all_stats[stat.to_sym].to_s || raise(ArgumentError, "unknown stat #{stat}")
|
16
|
+
end
|
17
|
+
|
18
|
+
on true do
|
19
|
+
write_plain 200, all_stats.map{|stat, value| "#{stat}: #{value}"}.join("\n")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'unicorn-cuba-base/memory_limit'
|
3
|
+
MemoryLimit.logger = Logger.new('/dev/null')
|
4
|
+
|
5
|
+
describe MemoryLimit do
|
6
|
+
subject do
|
7
|
+
MemoryLimit.new(10)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should raise MemoryLimitedExceededError when too much memory is borrowed' do
|
11
|
+
subject.borrow 8
|
12
|
+
subject.return 3
|
13
|
+
subject.borrow 4
|
14
|
+
subject.borrow 1
|
15
|
+
expect {
|
16
|
+
subject.borrow 1
|
17
|
+
}.to raise_error MemoryLimit::MemoryLimitedExceededError, 'memory limit exceeded'
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#get' do
|
21
|
+
it 'should yield limit left and borrow as much as returned string bytesize' do
|
22
|
+
subject.get do |limit|
|
23
|
+
limit.should == 10
|
24
|
+
'123'
|
25
|
+
end.should == '123'
|
26
|
+
subject.limit.should == 7
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should raise MemoryLimit::MemoryLimitedExceededError if retruned string is longer than the limit' do
|
30
|
+
expect {
|
31
|
+
subject.get do |limit|
|
32
|
+
'12345678901'
|
33
|
+
end
|
34
|
+
}.to raise_error MemoryLimit::MemoryLimitedExceededError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe MemoryLimit::IO do
|
39
|
+
it 'should limit reading from extended IO like object' do
|
40
|
+
test_file = Pathname.new('/tmp/memlimtest')
|
41
|
+
test_file.open('w+') { |io|
|
42
|
+
io.write '12345'
|
43
|
+
io.seek 0
|
44
|
+
|
45
|
+
subject.io io
|
46
|
+
io.read.should == '12345'
|
47
|
+
|
48
|
+
io.seek 0
|
49
|
+
io.truncate 0
|
50
|
+
io.write '678'
|
51
|
+
io.seek 0
|
52
|
+
io.read.should == '678'
|
53
|
+
|
54
|
+
io.seek 0
|
55
|
+
io.write '09123'
|
56
|
+
io.seek 0
|
57
|
+
|
58
|
+
expect {
|
59
|
+
io.read
|
60
|
+
}.to raise_error MemoryLimit::MemoryLimitedExceededError, 'memory limit exceeded'
|
61
|
+
}
|
62
|
+
test_file.unlink
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
require 'unicorn-cuba-base/root_logger'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
describe do
|
7
|
+
let! :log_out do
|
8
|
+
StringIO.new
|
9
|
+
end
|
10
|
+
|
11
|
+
subject do
|
12
|
+
RootLogger.new(log_out)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should log to given logger' do
|
16
|
+
subject.info 'hello world'
|
17
|
+
log_out.string.should include 'INFO'
|
18
|
+
log_out.string.should include 'hello world'
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'support for different logging levels' do
|
22
|
+
it 'should log info' do
|
23
|
+
subject.info 'hello world'
|
24
|
+
log_out.string.should include 'INFO'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should log warn' do
|
28
|
+
subject.warn 'hello world'
|
29
|
+
log_out.string.should include 'WARN'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should log error' do
|
33
|
+
subject.error 'hello world'
|
34
|
+
log_out.string.should include 'ERROR'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'class logger' do
|
39
|
+
it 'should report class name' do
|
40
|
+
TestApp = Class.new
|
41
|
+
subject.logger_for(TestApp).info 'hello world'
|
42
|
+
log_out.string.should include 'TestApp'
|
43
|
+
|
44
|
+
subject.logger_for(String).info 'hello world'
|
45
|
+
log_out.string.should include 'String'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should log exceptions' do
|
49
|
+
begin
|
50
|
+
raise RuntimeError, 'bad luck'
|
51
|
+
rescue => error
|
52
|
+
subject.logger_for(String).error 'hello world', error
|
53
|
+
end
|
54
|
+
log_out.string.should include 'hello world: RuntimeError: bad luck'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'unicorn-cuba-base'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "unicorn-cuba-base"
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jakub Pastuszek"]
|
12
|
+
s.date = "2013-07-16"
|
13
|
+
s.description = "web application base powered by Unicorn HTTP server and based on Cuba framework extended with additional Rack middleware and Cuba plugins"
|
14
|
+
s.email = "jpastuszek@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/unicorn-cuba-base.rb",
|
29
|
+
"lib/unicorn-cuba-base/memory_limit.rb",
|
30
|
+
"lib/unicorn-cuba-base/plugin/error_matcher.rb",
|
31
|
+
"lib/unicorn-cuba-base/plugin/logging.rb",
|
32
|
+
"lib/unicorn-cuba-base/plugin/memory_limit.rb",
|
33
|
+
"lib/unicorn-cuba-base/plugin/response_helpers.rb",
|
34
|
+
"lib/unicorn-cuba-base/rack/error_handling.rb",
|
35
|
+
"lib/unicorn-cuba-base/rack/memory_limit.rb",
|
36
|
+
"lib/unicorn-cuba-base/rack/unhandled_request.rb",
|
37
|
+
"lib/unicorn-cuba-base/root_logger.rb",
|
38
|
+
"lib/unicorn-cuba-base/stats.rb",
|
39
|
+
"lib/unicorn-cuba-base/stats_reporter.rb",
|
40
|
+
"spec/memory_limit_spec.rb",
|
41
|
+
"spec/root_logger_spec.rb",
|
42
|
+
"spec/spec_helper.rb",
|
43
|
+
"unicorn-cuba-base.gemspec"
|
44
|
+
]
|
45
|
+
s.homepage = "http://github.com/jpastuszek/unicorn-cuba-base"
|
46
|
+
s.licenses = ["MIT"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = "1.8.25"
|
49
|
+
s.summary = "web appliaction base powered by Unicorn and Cuba"
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_runtime_dependency(%q<cuba>, ["~> 3.0"])
|
56
|
+
s.add_runtime_dependency(%q<unicorn>, [">= 4.6.2"])
|
57
|
+
s.add_runtime_dependency(%q<raindrops>, ["~> 0.11"])
|
58
|
+
s.add_runtime_dependency(%q<cli>, ["~> 1.1.0"])
|
59
|
+
s.add_runtime_dependency(%q<facter>, ["~> 1.6.11"])
|
60
|
+
s.add_runtime_dependency(%q<ruby-ip>, ["~> 0.9"])
|
61
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.13"])
|
62
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.9"])
|
63
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<cuba>, ["~> 3.0"])
|
66
|
+
s.add_dependency(%q<unicorn>, [">= 4.6.2"])
|
67
|
+
s.add_dependency(%q<raindrops>, ["~> 0.11"])
|
68
|
+
s.add_dependency(%q<cli>, ["~> 1.1.0"])
|
69
|
+
s.add_dependency(%q<facter>, ["~> 1.6.11"])
|
70
|
+
s.add_dependency(%q<ruby-ip>, ["~> 0.9"])
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.13"])
|
72
|
+
s.add_dependency(%q<rdoc>, ["~> 3.9"])
|
73
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
74
|
+
end
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<cuba>, ["~> 3.0"])
|
77
|
+
s.add_dependency(%q<unicorn>, [">= 4.6.2"])
|
78
|
+
s.add_dependency(%q<raindrops>, ["~> 0.11"])
|
79
|
+
s.add_dependency(%q<cli>, ["~> 1.1.0"])
|
80
|
+
s.add_dependency(%q<facter>, ["~> 1.6.11"])
|
81
|
+
s.add_dependency(%q<ruby-ip>, ["~> 0.9"])
|
82
|
+
s.add_dependency(%q<rspec>, ["~> 2.13"])
|
83
|
+
s.add_dependency(%q<rdoc>, ["~> 3.9"])
|
84
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
metadata
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unicorn-cuba-base
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jakub Pastuszek
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cuba
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: unicorn
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 4.6.2
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 4.6.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: raindrops
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.11'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.11'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: cli
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.1.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.1.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: facter
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.6.11
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.6.11
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: ruby-ip
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.9'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.9'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.13'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '2.13'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rdoc
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '3.9'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '3.9'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: jeweler
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 1.8.4
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 1.8.4
|
158
|
+
description: web application base powered by Unicorn HTTP server and based on Cuba
|
159
|
+
framework extended with additional Rack middleware and Cuba plugins
|
160
|
+
email: jpastuszek@gmail.com
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files:
|
164
|
+
- LICENSE.txt
|
165
|
+
- README.md
|
166
|
+
files:
|
167
|
+
- .document
|
168
|
+
- .rspec
|
169
|
+
- Gemfile
|
170
|
+
- Gemfile.lock
|
171
|
+
- LICENSE.txt
|
172
|
+
- README.md
|
173
|
+
- Rakefile
|
174
|
+
- VERSION
|
175
|
+
- lib/unicorn-cuba-base.rb
|
176
|
+
- lib/unicorn-cuba-base/memory_limit.rb
|
177
|
+
- lib/unicorn-cuba-base/plugin/error_matcher.rb
|
178
|
+
- lib/unicorn-cuba-base/plugin/logging.rb
|
179
|
+
- lib/unicorn-cuba-base/plugin/memory_limit.rb
|
180
|
+
- lib/unicorn-cuba-base/plugin/response_helpers.rb
|
181
|
+
- lib/unicorn-cuba-base/rack/error_handling.rb
|
182
|
+
- lib/unicorn-cuba-base/rack/memory_limit.rb
|
183
|
+
- lib/unicorn-cuba-base/rack/unhandled_request.rb
|
184
|
+
- lib/unicorn-cuba-base/root_logger.rb
|
185
|
+
- lib/unicorn-cuba-base/stats.rb
|
186
|
+
- lib/unicorn-cuba-base/stats_reporter.rb
|
187
|
+
- spec/memory_limit_spec.rb
|
188
|
+
- spec/root_logger_spec.rb
|
189
|
+
- spec/spec_helper.rb
|
190
|
+
- unicorn-cuba-base.gemspec
|
191
|
+
homepage: http://github.com/jpastuszek/unicorn-cuba-base
|
192
|
+
licenses:
|
193
|
+
- MIT
|
194
|
+
post_install_message:
|
195
|
+
rdoc_options: []
|
196
|
+
require_paths:
|
197
|
+
- lib
|
198
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
199
|
+
none: false
|
200
|
+
requirements:
|
201
|
+
- - ! '>='
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
segments:
|
205
|
+
- 0
|
206
|
+
hash: -3548490658984311397
|
207
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
|
+
none: false
|
209
|
+
requirements:
|
210
|
+
- - ! '>='
|
211
|
+
- !ruby/object:Gem::Version
|
212
|
+
version: '0'
|
213
|
+
requirements: []
|
214
|
+
rubyforge_project:
|
215
|
+
rubygems_version: 1.8.25
|
216
|
+
signing_key:
|
217
|
+
specification_version: 3
|
218
|
+
summary: web appliaction base powered by Unicorn and Cuba
|
219
|
+
test_files: []
|