kirk 0.1.0-java
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.
- data/LICENSE +0 -0
- data/README.md +102 -0
- data/bin/kirk +3 -0
- data/lib/kirk.rb +55 -0
- data/lib/kirk/applications/config.rb +28 -0
- data/lib/kirk/applications/deploy_watcher.rb +138 -0
- data/lib/kirk/applications/hot_deployable.rb +50 -0
- data/lib/kirk/applications/rack.rb +21 -0
- data/lib/kirk/bootstrap.rb +62 -0
- data/lib/kirk/builder.rb +133 -0
- data/lib/kirk/cli.rb +52 -0
- data/lib/kirk/handler.rb +126 -0
- data/lib/kirk/input_stream.rb +261 -0
- data/lib/kirk/jetty.rb +18 -0
- data/lib/kirk/jetty/jetty-server-7.2.2.v20101205.jar +0 -0
- data/lib/kirk/jetty/servlet-api-2.5.jar +0 -0
- data/lib/kirk/native.jar +0 -0
- data/lib/kirk/server.rb +96 -0
- data/lib/kirk/version.rb +4 -0
- data/lib/rack/handler/kirk.rb +25 -0
- metadata +80 -0
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Kirk: a JRuby Rack server based on Jetty
|
2
|
+
|
3
|
+
Kirk is a wrapper around Jetty that hides all of the insanity and wraps your
|
4
|
+
Rack application in a loving embrace. Also, Kirk is probably the least HTTP
|
5
|
+
retarded ruby rack server out there.
|
6
|
+
|
7
|
+
### TL;DR
|
8
|
+
|
9
|
+
gem install kirk
|
10
|
+
rackup -s Kirk config.ru
|
11
|
+
|
12
|
+
### Features
|
13
|
+
|
14
|
+
Here is a brief highlight of some of the features available.
|
15
|
+
|
16
|
+
* 0 Downtime deploys: Deploy new versions of your rack application without
|
17
|
+
losing a single request. The basic strategy is similar to Passenger.
|
18
|
+
Touch a magic file in the application root and Kirk will reload the
|
19
|
+
application without dropping a single request... It's just magic.
|
20
|
+
|
21
|
+
* Request body streaming: Have a large request body to handle? Why wait until
|
22
|
+
receiving the entire thing before starting the work?
|
23
|
+
|
24
|
+
* HTTP goodness: `Transfer-Encoding: chunked`, `Content-Encoding: gzip` (and
|
25
|
+
deflate) support on the request.
|
26
|
+
|
27
|
+
* Concurrency: As it turns out, it's nice to not block your entire application
|
28
|
+
while waiting for an HTTP request to finish. It's also nice to be able to
|
29
|
+
handle more than 50 concurrent requests without burning over 5GB of RAM. Sure,
|
30
|
+
RAM is cheap, but using Kirk is cheaper ;)
|
31
|
+
|
32
|
+
* Run on the JVM: I, for one, am a fan of having a predictable GC, a JIT
|
33
|
+
compiler, and other goodness.
|
34
|
+
|
35
|
+
### Getting Started
|
36
|
+
|
37
|
+
To take advantage of the zero downtime redeploy features, you will need to
|
38
|
+
create a configuration file that describes to Kirk how to start and watch the
|
39
|
+
rack application. You can create the file anywhere. For example, let's say that
|
40
|
+
we are going to put the following configuration file at `/path/to/Kirkfile`.
|
41
|
+
|
42
|
+
# Set the log level to ALL.
|
43
|
+
log :level => :all
|
44
|
+
|
45
|
+
rack "/path/to/my/rackup/config.ru" do
|
46
|
+
# Set the host and / or port that this rack application will
|
47
|
+
# be available on. This defaults to "0.0.0.0:9090"
|
48
|
+
listen 80
|
49
|
+
|
50
|
+
# Set the host names that this rack application wll be available
|
51
|
+
# on. This defaults to "*"
|
52
|
+
hosts "example.com", "*.example.org"
|
53
|
+
|
54
|
+
# Set the file that controls the redeploys. This is relative to
|
55
|
+
# the applications root (the directory that the rackup file lives
|
56
|
+
# in). Touch this file to redepoy the application.
|
57
|
+
watch "REVISION"
|
58
|
+
end
|
59
|
+
|
60
|
+
rack "/path/to/another/rackup/config.ru" do
|
61
|
+
# More settings here
|
62
|
+
end
|
63
|
+
|
64
|
+
Once you have Kirk configured, start it up with the following command:
|
65
|
+
|
66
|
+
kirk -c /path/to/Kirkfile
|
67
|
+
|
68
|
+
... and you're off.
|
69
|
+
|
70
|
+
### Daemonizing Kirk
|
71
|
+
|
72
|
+
Use your OS features. For example, write an upstart script or use
|
73
|
+
`start-stop-daemon`.
|
74
|
+
|
75
|
+
### Logging to a file or syslog
|
76
|
+
|
77
|
+
Kirk just dumps logs to stdout, so just pipe Kirk to `logger`.
|
78
|
+
|
79
|
+
### Caveats
|
80
|
+
|
81
|
+
This is still a pretty new project and a lot of settings that should be
|
82
|
+
abstracted are still hardcoded in the source.
|
83
|
+
|
84
|
+
* Kirk requires JRuby 1.6.0 RC 1 or greater. This is due to a JRuby bug that
|
85
|
+
was fixed in the 1.6 branch but never backported to older versions of JRuby.
|
86
|
+
Crazy stuff happens without the bug fix.
|
87
|
+
|
88
|
+
* The JDBC drivers will keep connections open unless they are explicitly
|
89
|
+
cleaned up, something which Rack applications do not do. A future release of
|
90
|
+
the jdbc ruby drivers will correctly clean up after the JDBC garbage, but for
|
91
|
+
now, you will have to manually do it. If you are running a Rails app, add the
|
92
|
+
following to the config.ru file:
|
93
|
+
|
94
|
+
at_exit { ActiveRecord::Base.clear_all_connections! }
|
95
|
+
|
96
|
+
### Getting Help
|
97
|
+
|
98
|
+
Ping me (carllerche) on Freenode in #carlhuda
|
99
|
+
|
100
|
+
### Reporting Bugs
|
101
|
+
|
102
|
+
Just post them to the Github repository's issue tracker.
|
data/bin/kirk
ADDED
data/lib/kirk.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Kirk
|
2
|
+
# Make sure that the version of JRuby is new enough
|
3
|
+
unless (JRUBY_VERSION.split('.')[0..2].map(&:to_i) <=> [1, 6, 0]) >= 0
|
4
|
+
raise "Kirk requires JRuby 1.6.0 RC 1 or greater. This is due to " \
|
5
|
+
"a bug that was fixed in the 1.6 line but not backported to " \
|
6
|
+
"older versions of JRuby. If you want to use Kirk with older " \
|
7
|
+
"versions of JRuby, bug headius."
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'java'
|
11
|
+
require 'kirk/native'
|
12
|
+
require 'kirk/jetty'
|
13
|
+
require 'kirk/version'
|
14
|
+
|
15
|
+
import "java.util.concurrent.LinkedBlockingQueue"
|
16
|
+
import "java.util.concurrent.TimeUnit"
|
17
|
+
import "java.util.logging.Logger"
|
18
|
+
import "java.util.logging.Level"
|
19
|
+
import "java.util.logging.ConsoleHandler"
|
20
|
+
|
21
|
+
module Native
|
22
|
+
import "com.strobecorp.kirk.ApplicationConfig"
|
23
|
+
import "com.strobecorp.kirk.HotDeployableApplication"
|
24
|
+
import "com.strobecorp.kirk.LogFormatter"
|
25
|
+
end
|
26
|
+
|
27
|
+
module Applications
|
28
|
+
autoload :Config, 'kirk/applications/config'
|
29
|
+
autoload :DeployWatcher, 'kirk/applications/deploy_watcher'
|
30
|
+
autoload :HotDeployable, 'kirk/applications/hot_deployable'
|
31
|
+
autoload :Rack, 'kirk/applications/rack'
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'kirk/builder'
|
35
|
+
require 'kirk/handler'
|
36
|
+
require 'kirk/input_stream'
|
37
|
+
require 'kirk/server'
|
38
|
+
|
39
|
+
# Configure the logger
|
40
|
+
def self.logger
|
41
|
+
@logger ||= begin
|
42
|
+
logger = Logger.get_logger("org.eclipse.jetty.util.log")
|
43
|
+
logger.set_use_parent_handlers(false)
|
44
|
+
logger.add_handler logger_handler
|
45
|
+
logger
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.logger_handler
|
50
|
+
ConsoleHandler.new.tap do |handler|
|
51
|
+
handler.set_output_stream(java::lang::System.out)
|
52
|
+
handler.set_formatter(Native::LogFormatter.new)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Kirk
|
2
|
+
module Applications
|
3
|
+
class Config
|
4
|
+
include Native::ApplicationConfig
|
5
|
+
|
6
|
+
attr_accessor :hosts,
|
7
|
+
:listen,
|
8
|
+
:watch,
|
9
|
+
:rackup,
|
10
|
+
:application_path,
|
11
|
+
:bootstrap_path
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@hosts = []
|
15
|
+
@listen = listen
|
16
|
+
end
|
17
|
+
|
18
|
+
def application_path
|
19
|
+
@application_path || File.dirname(rackup)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Handle the java interface
|
23
|
+
alias getApplicationPath application_path
|
24
|
+
alias getRackupPath rackup
|
25
|
+
alias getBootstrapPath bootstrap_path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Kirk
|
2
|
+
class Applications::DeployWatcher
|
3
|
+
include Jetty::LifeCycle::Listener
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@apps, @magic_telephone, @workers = [], LinkedBlockingQueue.new, 0
|
7
|
+
@info = {}
|
8
|
+
@thread = Thread.new { run_loop }
|
9
|
+
@thread.abort_on_exception = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def stop
|
13
|
+
@magic_telephone.put :halt
|
14
|
+
@thread.join
|
15
|
+
end
|
16
|
+
|
17
|
+
def life_cycle_started(app)
|
18
|
+
@magic_telephone.put [ :watch, app ]
|
19
|
+
end
|
20
|
+
|
21
|
+
def life_cycle_stopping(app)
|
22
|
+
@magic_telephone.put [ :unwatch, app ]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Rest of the java interface
|
26
|
+
def life_cycle_failure(app, cause) ; end
|
27
|
+
def life_cycle_starting(app) ; end
|
28
|
+
def life_cycle_stopped(app) ; end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def run_loop
|
33
|
+
while true
|
34
|
+
# First, pull off messages
|
35
|
+
while msg = @magic_telephone.poll(50, TimeUnit::MILLISECONDS)
|
36
|
+
return if msg == :halt
|
37
|
+
handle_message(*msg)
|
38
|
+
end
|
39
|
+
|
40
|
+
check_apps
|
41
|
+
end
|
42
|
+
|
43
|
+
until @workers == 0
|
44
|
+
msg = @magic_telephone.take
|
45
|
+
handle_message(*msg)
|
46
|
+
end
|
47
|
+
|
48
|
+
cleanup
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_message(action, *args)
|
52
|
+
case action
|
53
|
+
when :watch
|
54
|
+
|
55
|
+
app, _ = *args
|
56
|
+
|
57
|
+
@apps |= [app]
|
58
|
+
|
59
|
+
@info[app] ||= {
|
60
|
+
:standby => LinkedBlockingQueue.new,
|
61
|
+
:last_modified => app.last_modified
|
62
|
+
}
|
63
|
+
|
64
|
+
warmup_standby_deploy(app)
|
65
|
+
|
66
|
+
when :unwatch
|
67
|
+
|
68
|
+
app, _ = *args
|
69
|
+
@apps.delete(app)
|
70
|
+
|
71
|
+
when :worker_complete
|
72
|
+
|
73
|
+
@workers -= 1
|
74
|
+
|
75
|
+
else
|
76
|
+
|
77
|
+
raise "Unknown action `#{action}`"
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def check_apps
|
83
|
+
@apps.each do |app|
|
84
|
+
new_last_modified = app.last_modified
|
85
|
+
info = @info[app]
|
86
|
+
|
87
|
+
if new_last_modified > info[:last_modified]
|
88
|
+
Kirk.logger.info("Reloading `#{app.application_path}`")
|
89
|
+
redeploy(app, new_last_modified)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def redeploy(app, mtime)
|
95
|
+
if key = app.key
|
96
|
+
|
97
|
+
queue = @info[app][:standby]
|
98
|
+
deploy = queue.poll
|
99
|
+
|
100
|
+
if deploy && deploy.key == key
|
101
|
+
app.deploy(deploy)
|
102
|
+
else
|
103
|
+
deploy.terminate if deploy
|
104
|
+
app.redeploy
|
105
|
+
end
|
106
|
+
|
107
|
+
warmup_standby_deploy(app)
|
108
|
+
else
|
109
|
+
app.redeploy
|
110
|
+
end
|
111
|
+
|
112
|
+
@info[app][:last_modified] = mtime
|
113
|
+
end
|
114
|
+
|
115
|
+
def warmup_standby_deploy(app)
|
116
|
+
queue = @info[app][:standby]
|
117
|
+
background { queue.put(app.build_deploy) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def cleanup
|
121
|
+
@apps.each do |app|
|
122
|
+
queue = @info[app][:standby]
|
123
|
+
|
124
|
+
while deploy = queue.poll
|
125
|
+
deploy.terminate
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def background
|
131
|
+
@workers += 1
|
132
|
+
Thread.new do
|
133
|
+
yield
|
134
|
+
@magic_telephone.put [ :worker_complete ]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Kirk
|
4
|
+
module Applications
|
5
|
+
# This class extends a native Java class
|
6
|
+
class HotDeployable < Native::HotDeployableApplication
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
super(config)
|
10
|
+
redeploy
|
11
|
+
end
|
12
|
+
|
13
|
+
def key
|
14
|
+
gemfile = "#{application_path}/Gemfile"
|
15
|
+
lockfile = "#{application_path}/Gemfile.lock"
|
16
|
+
|
17
|
+
if File.exist?(gemfile) && File.exist?(lockfile)
|
18
|
+
str = File.read(gemfile) + File.read(lockfile)
|
19
|
+
Digest::SHA1.hexdigest(str)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_watcher(watcher)
|
24
|
+
add_life_cycle_listener(watcher)
|
25
|
+
end
|
26
|
+
|
27
|
+
def application_path
|
28
|
+
config.application_path
|
29
|
+
end
|
30
|
+
|
31
|
+
def last_modified
|
32
|
+
mtimes = config.watch.map do |path|
|
33
|
+
path = File.expand_path(path, application_path)
|
34
|
+
File.exist?(path) ? File.mtime(path).to_i : 0
|
35
|
+
end
|
36
|
+
|
37
|
+
mtimes.max
|
38
|
+
end
|
39
|
+
|
40
|
+
def redeploy
|
41
|
+
deploy(build_deploy)
|
42
|
+
end
|
43
|
+
|
44
|
+
def deploy(d)
|
45
|
+
d.prepare
|
46
|
+
super(d)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
gem "rack", ">= 1.0.0"
|
2
|
+
require "rack"
|
3
|
+
|
4
|
+
module Kirk
|
5
|
+
module Applications
|
6
|
+
# This class extends a native Java class
|
7
|
+
class Rack < Jetty::AbstractHandler
|
8
|
+
def self.new(app)
|
9
|
+
inst = super()
|
10
|
+
inst.app = Handler.new(app)
|
11
|
+
inst
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :app
|
15
|
+
|
16
|
+
def handle(target, base_request, request, response)
|
17
|
+
app.handle(target, base_request, request, response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
module Kirk
|
4
|
+
import 'java.util.zip.GZIPInputStream'
|
5
|
+
import 'java.util.zip.InflaterInputStream'
|
6
|
+
|
7
|
+
class Bootstrap
|
8
|
+
def warmup(application_path)
|
9
|
+
Dir.chdir File.expand_path(application_path)
|
10
|
+
|
11
|
+
load_rubygems
|
12
|
+
|
13
|
+
load_bundle.tap do
|
14
|
+
add_kirk_to_load_path
|
15
|
+
|
16
|
+
load_rack
|
17
|
+
load_kirk
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(rackup)
|
22
|
+
app, options = Rack::Builder.parse_file(rackup)
|
23
|
+
|
24
|
+
Kirk::Handler.new(app)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load_rubygems
|
30
|
+
require 'rubygems'
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_bundle
|
34
|
+
if File.exist?('Gemfile')
|
35
|
+
require 'bundler/setup'
|
36
|
+
|
37
|
+
if File.exist?('Gemfile.lock')
|
38
|
+
require 'digest/sha1'
|
39
|
+
str = File.read('Gemfile') + File.read('Gemfile.lock')
|
40
|
+
Digest::SHA1.hexdigest(str)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_kirk_to_load_path
|
46
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_rack
|
50
|
+
gem "rack", ">= 1.0.0"
|
51
|
+
require 'rack'
|
52
|
+
end
|
53
|
+
|
54
|
+
def load_kirk
|
55
|
+
require 'kirk/version'
|
56
|
+
require 'kirk/input_stream'
|
57
|
+
require 'kirk/handler'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Kirk::Bootstrap.new
|
data/lib/kirk/builder.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
module Kirk
|
2
|
+
class MissingConfigFile < RuntimeError ; end
|
3
|
+
class MissingRackupFile < RuntimeError ; end
|
4
|
+
|
5
|
+
class Builder
|
6
|
+
|
7
|
+
VALID_LOG_LEVELS = %w(severe warning info config fine finer finest all)
|
8
|
+
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
def initialize(root = nil)
|
12
|
+
@root = root || Dir.pwd
|
13
|
+
@current = nil
|
14
|
+
@configs = []
|
15
|
+
@options = { :watcher => Applications::DeployWatcher.new }
|
16
|
+
end
|
17
|
+
|
18
|
+
def load(path)
|
19
|
+
path = expand_path(path)
|
20
|
+
|
21
|
+
with_root File.dirname(path) do
|
22
|
+
unless File.exist?(path)
|
23
|
+
raise MissingConfigFile, "config file `#{path}` does not exist"
|
24
|
+
end
|
25
|
+
|
26
|
+
instance_eval(File.read(path), path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def log(opts = {})
|
31
|
+
level = opts[:level]
|
32
|
+
raise "Invalid log level" unless VALID_LOG_LEVELS.include?(level.to_s)
|
33
|
+
@options[:log_level] = level.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def rack(rackup)
|
37
|
+
rackup = expand_path(rackup)
|
38
|
+
|
39
|
+
unless File.exist?(rackup)
|
40
|
+
raise MissingRackupFile, "rackup file `#{rackup}` does not exist"
|
41
|
+
end
|
42
|
+
|
43
|
+
@current = new_config
|
44
|
+
@current.rackup = rackup
|
45
|
+
|
46
|
+
yield if block_given?
|
47
|
+
|
48
|
+
ensure
|
49
|
+
@configs << @current
|
50
|
+
@current = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def hosts(*hosts)
|
54
|
+
@current.hosts.concat hosts
|
55
|
+
end
|
56
|
+
|
57
|
+
def listen(listen)
|
58
|
+
listen = listen.to_s
|
59
|
+
listen = ":#{listen}" unless listen.index(':')
|
60
|
+
listen = "0.0.0.0#{listen}" if listen.index(':') == 0
|
61
|
+
|
62
|
+
@current.listen = listen
|
63
|
+
end
|
64
|
+
|
65
|
+
def watch(*watch)
|
66
|
+
@current.watch = watch
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_handler
|
70
|
+
handlers = @configs.map do |c|
|
71
|
+
Jetty::ContextHandler.new.tap do |ctx|
|
72
|
+
# Set the virtual hosts
|
73
|
+
unless c.hosts.empty?
|
74
|
+
ctx.set_virtual_hosts(c.hosts)
|
75
|
+
end
|
76
|
+
|
77
|
+
application = Applications::HotDeployable.new(c)
|
78
|
+
application.add_watcher(watcher)
|
79
|
+
|
80
|
+
ctx.set_connector_names [c.listen]
|
81
|
+
ctx.set_handler application
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Jetty::ContextHandlerCollection.new.tap do |collection|
|
86
|
+
collection.set_handlers(handlers)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_connectors
|
91
|
+
@connectors = {}
|
92
|
+
|
93
|
+
@configs.each do |config|
|
94
|
+
next if @connectors.key?(config.listen)
|
95
|
+
|
96
|
+
host, port = config.listen.split(':')
|
97
|
+
|
98
|
+
connector = Jetty::SelectChannelConnector.new
|
99
|
+
connector.set_host(host)
|
100
|
+
connector.set_port(port.to_i)
|
101
|
+
|
102
|
+
@connectors[config.listen] = connector
|
103
|
+
end
|
104
|
+
|
105
|
+
@connectors.values
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def watcher
|
111
|
+
@options[:watcher]
|
112
|
+
end
|
113
|
+
|
114
|
+
def with_root(root)
|
115
|
+
old, @root = @root, root
|
116
|
+
yield
|
117
|
+
ensure
|
118
|
+
@root = old
|
119
|
+
end
|
120
|
+
|
121
|
+
def expand_path(path)
|
122
|
+
File.expand_path(path, @root)
|
123
|
+
end
|
124
|
+
|
125
|
+
def new_config
|
126
|
+
Applications::Config.new.tap do |config|
|
127
|
+
config.listen = '0.0.0.0:9090'
|
128
|
+
config.watch = [ 'REVISION' ]
|
129
|
+
config.bootstrap_path = File.expand_path('../bootstrap.rb', __FILE__)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/kirk/cli.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'kirk'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Kirk
|
5
|
+
class CLI
|
6
|
+
def self.start(argv)
|
7
|
+
new(argv).tap { |inst| inst.run }
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(argv)
|
11
|
+
@argv = argv.dup
|
12
|
+
@options = default_options
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
parse!
|
17
|
+
|
18
|
+
server = Kirk::Server.build(config)
|
19
|
+
server.start
|
20
|
+
server.join
|
21
|
+
rescue Exception => e
|
22
|
+
abort "[ERROR] #{e.message}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def config
|
28
|
+
@options[:config]
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_options
|
32
|
+
{ :config => "#{Dir.pwd}/Kirkfile" }
|
33
|
+
end
|
34
|
+
|
35
|
+
def parser
|
36
|
+
@parser ||= OptionParser.new do |opts|
|
37
|
+
opts.banner = "Usage: kirk [options] <command> [<args>]"
|
38
|
+
|
39
|
+
opts.separator ""
|
40
|
+
opts.separator "Server options:"
|
41
|
+
|
42
|
+
opts.on("-c", "--config FILE", "Load options from a config file") do |file|
|
43
|
+
@options[:config] = file
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse!
|
49
|
+
parser.parse! @argv
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/kirk/handler.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module Kirk
|
2
|
+
class Handler
|
3
|
+
# Required environment variable keys
|
4
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
5
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
6
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
7
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
8
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
9
|
+
SERVER_PORT = 'SERVER_PORT'.freeze
|
10
|
+
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
11
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
12
|
+
REQUEST_URI = 'REQUEST_URI'.freeze
|
13
|
+
REMOTE_HOST = 'REMOTE_HOST'.freeze
|
14
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
15
|
+
REMOTE_USER = 'REMOTE_USER'.freeze
|
16
|
+
|
17
|
+
# Rack specific variable keys
|
18
|
+
RACK_VERSION = 'rack.version'.freeze
|
19
|
+
RACK_URL_SCHEME = 'rack.url_scheme'.freeze
|
20
|
+
RACK_INPUT = 'rack.input'.freeze
|
21
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
22
|
+
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
23
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
24
|
+
RACK_RUN_ONCE = 'rack.run_once'.freeze
|
25
|
+
HTTP_PREFIX = 'HTTP_'.freeze
|
26
|
+
|
27
|
+
# Rack response header names
|
28
|
+
CONTENT_TYPE_RESP = 'Content-Type'
|
29
|
+
CONTENT_LENGTH_RESP = 'Content-Length'
|
30
|
+
|
31
|
+
# Kirk information
|
32
|
+
SERVER = "#{NAME} #{VERSION}".freeze
|
33
|
+
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
34
|
+
|
35
|
+
DEFAULT_RACK_ENV = {
|
36
|
+
SERVER_SOFTWARE => SERVER,
|
37
|
+
|
38
|
+
# Rack stuff
|
39
|
+
RACK_ERRORS => STDERR,
|
40
|
+
RACK_MULTITHREAD => true,
|
41
|
+
RACK_MULTIPROCESS => false,
|
42
|
+
RACK_RUN_ONCE => false,
|
43
|
+
}
|
44
|
+
|
45
|
+
CONTENT_LENGTH_TYPE_REGEXP = /^Content-(?:Type|Length)$/i
|
46
|
+
|
47
|
+
def initialize(app)
|
48
|
+
@app = app
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle(target, base_request, request, response)
|
52
|
+
begin
|
53
|
+
env = DEFAULT_RACK_ENV.merge(
|
54
|
+
REQUEST_URI => request.getRequestURI,
|
55
|
+
PATH_INFO => request.get_path_info,
|
56
|
+
REQUEST_METHOD => request.get_method || "GET",
|
57
|
+
RACK_URL_SCHEME => request.get_scheme || "http",
|
58
|
+
QUERY_STRING => request.get_query_string || "",
|
59
|
+
SERVER_NAME => request.get_server_name || "",
|
60
|
+
REMOTE_HOST => request.get_remote_host || "",
|
61
|
+
REMOTE_ADDR => request.get_remote_addr || "",
|
62
|
+
REMOTE_USER => request.get_remote_user || "",
|
63
|
+
SERVER_PORT => request.get_server_port.to_s,
|
64
|
+
RACK_VERSION => ::Rack::VERSION)
|
65
|
+
|
66
|
+
# Process the content length
|
67
|
+
if (content_length = request.get_content_length) >= 0
|
68
|
+
env[CONTENT_LENGTH] = content_length.to_s
|
69
|
+
else
|
70
|
+
env[CONTENT_LENGTH] = "0"
|
71
|
+
end
|
72
|
+
|
73
|
+
if (content_type = request.get_content_type)
|
74
|
+
env[CONTENT_TYPE] = content_type unless content_type == ''
|
75
|
+
end
|
76
|
+
|
77
|
+
request.get_header_names.each do |header|
|
78
|
+
next if header =~ CONTENT_LENGTH_TYPE_REGEXP
|
79
|
+
value = request.get_header(header)
|
80
|
+
|
81
|
+
header.tr! '-', '_'
|
82
|
+
header.upcase!
|
83
|
+
|
84
|
+
header = "#{HTTP_PREFIX}#{header}"
|
85
|
+
env[header] = value unless env.key?(header) || value == ''
|
86
|
+
end
|
87
|
+
|
88
|
+
input = request.get_input_stream
|
89
|
+
|
90
|
+
case env['HTTP_CONTENT_ENCODING']
|
91
|
+
when 'deflate' then input = InflaterInputStream.new(input)
|
92
|
+
when 'gzip' then input = GZIPInputStream.new(input)
|
93
|
+
end
|
94
|
+
|
95
|
+
input = InputStream.new(input)
|
96
|
+
env[RACK_INPUT] = input
|
97
|
+
|
98
|
+
# Dispatch the request
|
99
|
+
status, headers, body = @app.call(env)
|
100
|
+
|
101
|
+
response.set_status(status.to_i)
|
102
|
+
|
103
|
+
headers.each do |header, value|
|
104
|
+
case header
|
105
|
+
when CONTENT_TYPE_RESP
|
106
|
+
response.set_content_type(value)
|
107
|
+
when CONTENT_LENGTH_RESP
|
108
|
+
response.set_content_length(value.to_i)
|
109
|
+
else
|
110
|
+
response.set_header(header, value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
buffer = response.get_output_stream
|
115
|
+
body.each do |s|
|
116
|
+
buffer.write(s.to_java_bytes)
|
117
|
+
end
|
118
|
+
|
119
|
+
body.close if body.respond_to?(:close)
|
120
|
+
ensure
|
121
|
+
input.recycle if input
|
122
|
+
request.set_handled(true)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
module Kirk
|
4
|
+
class InputStream
|
5
|
+
READL_SIZE = 1_024
|
6
|
+
CHUNK_SIZE = 8_192
|
7
|
+
BUFFER_SIZE = 1_024 * 50
|
8
|
+
|
9
|
+
import "java.io.File"
|
10
|
+
import "java.io.RandomAccessFile"
|
11
|
+
import "java.nio.ByteBuffer"
|
12
|
+
import "java.nio.channels.Channels"
|
13
|
+
|
14
|
+
# For our ROFL scale needs
|
15
|
+
import "java.util.concurrent.LinkedBlockingQueue"
|
16
|
+
|
17
|
+
BUFFER_POOL = LinkedBlockingQueue.new
|
18
|
+
TMPFILE_PREFIX = "rackinput".to_java_string
|
19
|
+
TMPFILE_SUFFIX = "".to_java_string
|
20
|
+
|
21
|
+
def initialize(io)
|
22
|
+
@io = channelize(io)
|
23
|
+
@buffer = grab_buffer
|
24
|
+
@filebuf = nil
|
25
|
+
@read = 0
|
26
|
+
@position = 0
|
27
|
+
@eof = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def read(size = nil, buffer = nil)
|
31
|
+
one_loop = nil
|
32
|
+
read_all = size.nil?
|
33
|
+
|
34
|
+
buffer ? buffer.slice!(0..-1) : buffer = ''
|
35
|
+
|
36
|
+
raise ArgumentError, "negative length #{size} given" if size && size < 0
|
37
|
+
|
38
|
+
loop do
|
39
|
+
limit = size && size < CHUNK_SIZE ? size : CHUNK_SIZE
|
40
|
+
|
41
|
+
len = read_chunk(limit, buffer)
|
42
|
+
|
43
|
+
break unless len
|
44
|
+
|
45
|
+
one_loop = true
|
46
|
+
|
47
|
+
break if size && ( size -= len ) <= 0
|
48
|
+
end
|
49
|
+
|
50
|
+
return "" if read_all && !one_loop
|
51
|
+
|
52
|
+
one_loop && buffer
|
53
|
+
end
|
54
|
+
|
55
|
+
def gets(sep = $/)
|
56
|
+
return read unless sep
|
57
|
+
|
58
|
+
sep = "#{$/}#{$/}" if sep == ""
|
59
|
+
buffer = ''
|
60
|
+
curpos = pos
|
61
|
+
|
62
|
+
while chunk = read(READL_SIZE)
|
63
|
+
buffer << chunk
|
64
|
+
|
65
|
+
if i = buffer.index(sep, 0)
|
66
|
+
i += sep.bytesize
|
67
|
+
buffer.slice!(i..-1)
|
68
|
+
seek(curpos + buffer.bytesize)
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
buffer
|
74
|
+
end
|
75
|
+
|
76
|
+
def each
|
77
|
+
while chunk = read(CHUNK_SIZE)
|
78
|
+
yield chunk
|
79
|
+
end
|
80
|
+
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def pos
|
85
|
+
@position
|
86
|
+
end
|
87
|
+
|
88
|
+
def seek(pos)
|
89
|
+
raise Errno::EINVAL, "Invalid argument - invalid seek value" if pos < 0
|
90
|
+
@position = pos
|
91
|
+
end
|
92
|
+
|
93
|
+
def rewind
|
94
|
+
seek(0)
|
95
|
+
end
|
96
|
+
|
97
|
+
def close
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def recycle
|
102
|
+
BUFFER_POOL.put(@buffer)
|
103
|
+
@buffer = nil
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def channelize(stream)
|
110
|
+
Channels.new_channel(stream)
|
111
|
+
end
|
112
|
+
|
113
|
+
def grab_buffer
|
114
|
+
BUFFER_POOL.poll || ByteBuffer.allocate(BUFFER_SIZE)
|
115
|
+
end
|
116
|
+
|
117
|
+
def read_chunk(size, string)
|
118
|
+
missing = size - ( @read - @position )
|
119
|
+
|
120
|
+
if @filebuf || @read + missing > BUFFER_SIZE
|
121
|
+
|
122
|
+
rotate_to_tmp_file unless @filebuf
|
123
|
+
read_chunk_from_tmp_file(size, string)
|
124
|
+
|
125
|
+
else
|
126
|
+
|
127
|
+
read_chunk_from_mem(missing, string)
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def read_chunk_from_mem(missing, string)
|
133
|
+
# We gonna have to read from the input stream
|
134
|
+
if missing > 0 && !@eof
|
135
|
+
|
136
|
+
# Set the new buffer limit to the amount that is going
|
137
|
+
# to be read
|
138
|
+
@buffer.position(@read).limit(@read + missing)
|
139
|
+
|
140
|
+
# Read into the buffer
|
141
|
+
len = @io.read(@buffer)
|
142
|
+
|
143
|
+
# Bail if we're at the EOF
|
144
|
+
if len == -1
|
145
|
+
@eof = true
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
@read += len
|
150
|
+
|
151
|
+
# We're at the end
|
152
|
+
elsif @position == @read
|
153
|
+
|
154
|
+
return
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
# Now move the amount read into the string
|
159
|
+
@buffer.position(@position).limit(@read)
|
160
|
+
|
161
|
+
append_buffer_to_string(@buffer, string)
|
162
|
+
end
|
163
|
+
|
164
|
+
def read_chunk_from_tmp_file(size, string)
|
165
|
+
|
166
|
+
if @read < @position && !@eof
|
167
|
+
|
168
|
+
return unless buffer_to(@position)
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
@buffer.clear.limit(size)
|
173
|
+
|
174
|
+
if @read > @position
|
175
|
+
|
176
|
+
@filebuf.position(@position)
|
177
|
+
@filebuf.read(@buffer)
|
178
|
+
|
179
|
+
elsif @eof
|
180
|
+
|
181
|
+
return
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
unless @eof
|
186
|
+
|
187
|
+
offset = @buffer.position
|
188
|
+
len = @io.read(@buffer)
|
189
|
+
|
190
|
+
if len == -1
|
191
|
+
|
192
|
+
@eof = true
|
193
|
+
return if offset == 0
|
194
|
+
|
195
|
+
else
|
196
|
+
|
197
|
+
@filebuf.position(@read)
|
198
|
+
@filebuf.write(@buffer.flip.position(offset))
|
199
|
+
|
200
|
+
@read += len
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
@buffer.position(0)
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
append_buffer_to_string(@buffer, string)
|
209
|
+
end
|
210
|
+
|
211
|
+
def buffer_to(position)
|
212
|
+
until @read == position
|
213
|
+
limit = position - @read
|
214
|
+
limit = BUFFER_SIZE if limit > BUFFER_SIZE
|
215
|
+
|
216
|
+
@buffer.clear.limit(limit)
|
217
|
+
|
218
|
+
len = @io.read(@buffer)
|
219
|
+
|
220
|
+
if len == -1
|
221
|
+
@eof = true
|
222
|
+
return
|
223
|
+
end
|
224
|
+
|
225
|
+
@buffer.flip
|
226
|
+
|
227
|
+
@filebuf.position(@read)
|
228
|
+
@filebuf.write(@buffer)
|
229
|
+
|
230
|
+
@read += len
|
231
|
+
end
|
232
|
+
|
233
|
+
true
|
234
|
+
end
|
235
|
+
|
236
|
+
def append_buffer_to_string(buffer, string)
|
237
|
+
len = buffer.limit - buffer.position
|
238
|
+
bytes = Java::byte[len].new
|
239
|
+
buffer.get(bytes)
|
240
|
+
string << String.from_java_bytes(bytes)
|
241
|
+
@position += len
|
242
|
+
len
|
243
|
+
end
|
244
|
+
|
245
|
+
def rotate_to_tmp_file
|
246
|
+
@buffer.position(0).limit(@read)
|
247
|
+
|
248
|
+
@filebuf = create_tmp_file
|
249
|
+
@filebuf.write @buffer
|
250
|
+
|
251
|
+
@buffer.clear
|
252
|
+
end
|
253
|
+
|
254
|
+
def create_tmp_file
|
255
|
+
file = File.create_temp_file(TMPFILE_PREFIX, TMPFILE_SUFFIX)
|
256
|
+
file.delete_on_exit
|
257
|
+
|
258
|
+
RandomAccessFile.new(file, "rw").channel
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
data/lib/kirk/jetty.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "kirk/jetty/jetty-server-7.2.2.v20101205"
|
2
|
+
require "kirk/jetty/servlet-api-2.5"
|
3
|
+
|
4
|
+
module Kirk
|
5
|
+
module Jetty
|
6
|
+
# Gimme Jetty
|
7
|
+
import "org.eclipse.jetty.server.nio.SelectChannelConnector"
|
8
|
+
import "org.eclipse.jetty.server.handler.AbstractHandler"
|
9
|
+
import "org.eclipse.jetty.server.handler.ContextHandler"
|
10
|
+
import "org.eclipse.jetty.server.handler.ContextHandlerCollection"
|
11
|
+
import "org.eclipse.jetty.server.Server"
|
12
|
+
import "org.eclipse.jetty.util.component.LifeCycle"
|
13
|
+
import "org.eclipse.jetty.util.log.Log"
|
14
|
+
import "org.eclipse.jetty.util.log.JavaUtilLog"
|
15
|
+
|
16
|
+
Log.set_log Jetty::JavaUtilLog.new
|
17
|
+
end
|
18
|
+
end
|
Binary file
|
Binary file
|
data/lib/kirk/native.jar
ADDED
Binary file
|
data/lib/kirk/server.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Kirk
|
2
|
+
class Server
|
3
|
+
def self.build(file = nil, &blk)
|
4
|
+
root = File.dirname(file) if file
|
5
|
+
builder = Builder.new(root)
|
6
|
+
|
7
|
+
if file && !File.exist?(file)
|
8
|
+
raise MissingConfigFile, "config file `#{file}` does not exist"
|
9
|
+
end
|
10
|
+
|
11
|
+
file ? builder.instance_eval(File.read(file), file) :
|
12
|
+
builder.instance_eval(&blk)
|
13
|
+
|
14
|
+
options = builder.options
|
15
|
+
options[:connectors] = builder.to_connectors
|
16
|
+
|
17
|
+
new(builder.to_handler, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.start(handler, options = {})
|
21
|
+
new(handler, options).tap do |server|
|
22
|
+
server.start
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(handler, options = {})
|
27
|
+
if Jetty::AbstractHandler === handler
|
28
|
+
@handler = handler
|
29
|
+
elsif handler.respond_to?(:call)
|
30
|
+
@handler = Applications::Rack.new(handler)
|
31
|
+
else
|
32
|
+
raise "#{handler.inspect} is not a valid Rack application"
|
33
|
+
end
|
34
|
+
|
35
|
+
@options = options
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
@server = Jetty::Server.new.tap do |server|
|
40
|
+
connectors.each do |conn|
|
41
|
+
server.add_connector(conn)
|
42
|
+
end
|
43
|
+
|
44
|
+
server.set_handler(@handler)
|
45
|
+
end
|
46
|
+
|
47
|
+
configure!
|
48
|
+
|
49
|
+
@server.start
|
50
|
+
end
|
51
|
+
|
52
|
+
def join
|
53
|
+
@server.join
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop
|
57
|
+
watcher.stop if watcher
|
58
|
+
@server.stop
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def configure!
|
64
|
+
Kirk.logger.set_level log_level
|
65
|
+
end
|
66
|
+
|
67
|
+
def connectors
|
68
|
+
@options[:connectors] ||=
|
69
|
+
[ Jetty::SelectChannelConnector.new.tap do |conn|
|
70
|
+
host = @options[:host] || '0.0.0.0'
|
71
|
+
port = @options[:port] || 9090
|
72
|
+
|
73
|
+
conn.set_host(host)
|
74
|
+
conn.set_port(port.to_i)
|
75
|
+
end ]
|
76
|
+
end
|
77
|
+
|
78
|
+
def watcher
|
79
|
+
@options[:watcher]
|
80
|
+
end
|
81
|
+
|
82
|
+
def log_level
|
83
|
+
case (@options[:log_level] || "info").to_s
|
84
|
+
when "severe" then Level::SEVERE
|
85
|
+
when "warning" then Level::WARNING
|
86
|
+
when "info" then Level::INFO
|
87
|
+
when "config" then Level::CONFIG
|
88
|
+
when "fine" then Level::FINE
|
89
|
+
when "finer" then Level::FINER
|
90
|
+
when "finest" then Level::FINEST
|
91
|
+
when "all" then Level::ALL
|
92
|
+
else Level::INFO
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/kirk/version.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'kirk'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Handler
|
6
|
+
class Kirk
|
7
|
+
def self.run(app, options = {})
|
8
|
+
options[:host] = options[:Host]
|
9
|
+
options[:port] = options[:Port]
|
10
|
+
|
11
|
+
options[:log_level] ||= "warning"
|
12
|
+
|
13
|
+
server = ::Kirk::Server.new(app, options)
|
14
|
+
|
15
|
+
yield server if block_given?
|
16
|
+
|
17
|
+
# Tears :'(
|
18
|
+
trap(:INT) { server.stop }
|
19
|
+
|
20
|
+
server.start
|
21
|
+
server.join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kirk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: java
|
7
|
+
authors:
|
8
|
+
- Carl Lerche
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-02 00:00:00 -08:00
|
14
|
+
default_executable: kirk
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: |
|
18
|
+
Kirk is a wrapper around Jetty that hides all of the insanity and
|
19
|
+
wraps your Rack application in a loving embrace. Also, Kirk is
|
20
|
+
probably one of the least HTTP retarded ruby rack server out there.
|
21
|
+
|
22
|
+
email:
|
23
|
+
- me@carllerche.com
|
24
|
+
executables:
|
25
|
+
- kirk
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/kirk.rb
|
32
|
+
- lib/kirk/bootstrap.rb
|
33
|
+
- lib/kirk/builder.rb
|
34
|
+
- lib/kirk/cli.rb
|
35
|
+
- lib/kirk/handler.rb
|
36
|
+
- lib/kirk/input_stream.rb
|
37
|
+
- lib/kirk/jetty.rb
|
38
|
+
- lib/kirk/server.rb
|
39
|
+
- lib/kirk/version.rb
|
40
|
+
- lib/kirk/applications/config.rb
|
41
|
+
- lib/kirk/applications/deploy_watcher.rb
|
42
|
+
- lib/kirk/applications/hot_deployable.rb
|
43
|
+
- lib/kirk/applications/rack.rb
|
44
|
+
- lib/rack/handler/kirk.rb
|
45
|
+
- lib/kirk/native.jar
|
46
|
+
- lib/kirk/jetty/jetty-server-7.2.2.v20101205.jar
|
47
|
+
- lib/kirk/jetty/servlet-api-2.5.jar
|
48
|
+
- bin/kirk
|
49
|
+
- README.md
|
50
|
+
- LICENSE
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/carllerche/kirk
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 1.3.6
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: kirk
|
75
|
+
rubygems_version: 1.4.2
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A JRuby Rack Server Based on Jetty
|
79
|
+
test_files: []
|
80
|
+
|