mizuno 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Rakefile +19 -0
- data/bin/mizuno +3 -4
- data/lib/java/log4j-1.2.16.jar +0 -0
- data/lib/java/rewindable-input-stream.jar +0 -0
- data/lib/java/slf4j-api-1.6.4.jar +0 -0
- data/lib/java/slf4j-log4j12-1.6.4.jar +0 -0
- data/lib/mizuno.rb +14 -4
- data/lib/mizuno/choices.rb +158 -0
- data/lib/mizuno/http_server.rb +102 -18
- data/lib/mizuno/java_logger.rb +37 -0
- data/lib/mizuno/rack/chunked.rb +19 -0
- data/lib/mizuno/rack_servlet.rb +65 -43
- data/lib/mizuno/runner.rb +244 -0
- data/lib/mizuno/version.rb +3 -0
- data/mizuno.gemspec +14 -4
- metadata +55 -15
data/.gitignore
ADDED
data/Rakefile
CHANGED
@@ -78,3 +78,22 @@ namespace :jetty do
|
|
78
78
|
puts "Update complete."
|
79
79
|
end
|
80
80
|
end
|
81
|
+
|
82
|
+
namespace :java do
|
83
|
+
desc "Build bundled Java source files."
|
84
|
+
task :build do
|
85
|
+
system(<<-END)
|
86
|
+
javac -classpath lib/java/servlet-api-3.0.jar \
|
87
|
+
src/org/jruby/rack/servlet/RewindableInputStream.java
|
88
|
+
jar cf lib/java/rewindable-input-stream.jar -C src/ \
|
89
|
+
org/jruby/rack/servlet/RewindableInputStream.class
|
90
|
+
END
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Clean up after building."
|
94
|
+
task :clean do
|
95
|
+
system(<<-END)
|
96
|
+
rm src/org/jruby/rack/servlet/RewindableInputStream.class
|
97
|
+
END
|
98
|
+
end
|
99
|
+
end
|
data/bin/mizuno
CHANGED
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
raise("Mizuno only runs on JRuby.") unless (RUBY_PLATFORM =~ /java/)
|
4
4
|
|
5
|
-
require '
|
5
|
+
require 'rubygems'
|
6
6
|
require 'mizuno'
|
7
|
+
require 'mizuno/runner'
|
7
8
|
|
8
|
-
|
9
|
-
server.options[:server] = 'mizuno'
|
10
|
-
server.start
|
9
|
+
Mizuno::Runner.start!
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/mizuno.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
1
|
#
|
2
|
-
# A Rack handler for Jetty
|
2
|
+
# A Rack handler for Jetty 8.
|
3
3
|
#
|
4
4
|
# Written by Don Werve <don@madwombat.com>
|
5
5
|
#
|
6
6
|
|
7
|
-
#
|
8
|
-
|
7
|
+
# Save our launch environment for spawning children later.
|
8
|
+
module Mizuno
|
9
|
+
LAUNCH_ENV = $LOAD_PATH.map { |i| "-I#{i}" }.push($0)
|
10
|
+
end
|
9
11
|
|
10
|
-
# Load
|
12
|
+
# Load up Java dependencies.
|
13
|
+
require 'java'
|
11
14
|
jars = File.join(File.dirname(__FILE__), 'java', '*.jar')
|
12
15
|
Dir[jars].each { |j| require j }
|
13
16
|
|
17
|
+
# Tell log4j not to complain to the console about a missing
|
18
|
+
# log4j.properties file, as we configure it programmatically in
|
19
|
+
# Mizuno::HttpServer (http://stackoverflow.com/questions/6849887)
|
20
|
+
Java.org.apache.log4j.Logger.getRootLogger.setLevel( \
|
21
|
+
Java.org.apache.log4j.Level::INFO)
|
22
|
+
|
14
23
|
require 'rack'
|
24
|
+
require 'mizuno/rack/chunked'
|
15
25
|
require 'mizuno/rack_servlet'
|
16
26
|
require 'mizuno/http_server'
|
@@ -0,0 +1,158 @@
|
|
1
|
+
Choice.options do
|
2
|
+
separator ''
|
3
|
+
separator 'Ruby options: '
|
4
|
+
|
5
|
+
option :eval do
|
6
|
+
short '-e'
|
7
|
+
long '--eval'
|
8
|
+
desc 'evaluate a line of code'
|
9
|
+
default nil
|
10
|
+
end
|
11
|
+
|
12
|
+
option :debug do
|
13
|
+
short '-d'
|
14
|
+
long '--debug'
|
15
|
+
desc 'set debugging flags (set $DEBUG to true)'
|
16
|
+
default false
|
17
|
+
end
|
18
|
+
|
19
|
+
option :warn do
|
20
|
+
short '-w'
|
21
|
+
long '--warn'
|
22
|
+
desc 'turn warnings on for your script'
|
23
|
+
default false
|
24
|
+
end
|
25
|
+
|
26
|
+
option :include do
|
27
|
+
short '-I'
|
28
|
+
long '--include *PATHS'
|
29
|
+
desc 'specify $LOAD_PATH (may be used more than once)'
|
30
|
+
default []
|
31
|
+
end
|
32
|
+
|
33
|
+
option :require do
|
34
|
+
short '-r'
|
35
|
+
long '--require *GEMS'
|
36
|
+
desc 'require a gem (may be used more than once)'
|
37
|
+
default []
|
38
|
+
end
|
39
|
+
|
40
|
+
separator ''
|
41
|
+
separator 'Rack options: '
|
42
|
+
|
43
|
+
option :host do
|
44
|
+
short '-o'
|
45
|
+
long '--host'
|
46
|
+
desc 'the address to listen on'
|
47
|
+
default '0.0.0.0'
|
48
|
+
end
|
49
|
+
|
50
|
+
option :port do
|
51
|
+
short '-p'
|
52
|
+
long '--port'
|
53
|
+
desc 'the port to listen on'
|
54
|
+
cast Integer
|
55
|
+
default 9292
|
56
|
+
end
|
57
|
+
|
58
|
+
option :env do
|
59
|
+
short '-E'
|
60
|
+
long '--env'
|
61
|
+
desc 'application environment'
|
62
|
+
default 'development'
|
63
|
+
end
|
64
|
+
|
65
|
+
option :threads do
|
66
|
+
long '--threads'
|
67
|
+
desc 'maximum size of the thread pool'
|
68
|
+
cast Integer
|
69
|
+
default 50
|
70
|
+
end
|
71
|
+
|
72
|
+
option :log do
|
73
|
+
long '--log'
|
74
|
+
desc 'logfile (defaults to stderr)'
|
75
|
+
default nil
|
76
|
+
end
|
77
|
+
|
78
|
+
separator ''
|
79
|
+
separator 'Daemonization: '
|
80
|
+
|
81
|
+
option :daemonize do
|
82
|
+
short '-D'
|
83
|
+
long '--start'
|
84
|
+
desc 'detach and run as a daemon'
|
85
|
+
default false
|
86
|
+
end
|
87
|
+
|
88
|
+
option :stop do
|
89
|
+
long '--stop'
|
90
|
+
desc 'stop a running daemon'
|
91
|
+
default false
|
92
|
+
end
|
93
|
+
|
94
|
+
option :kill do
|
95
|
+
long '--stop'
|
96
|
+
desc 'stop a running daemon'
|
97
|
+
default false
|
98
|
+
end
|
99
|
+
|
100
|
+
option :status do
|
101
|
+
long '--status'
|
102
|
+
desc 'get the status of a running daemon'
|
103
|
+
default false
|
104
|
+
end
|
105
|
+
|
106
|
+
option :reload do
|
107
|
+
long '--reload'
|
108
|
+
desc 'reload a running daemon'
|
109
|
+
default false
|
110
|
+
end
|
111
|
+
|
112
|
+
option :pidfile do
|
113
|
+
short '-P'
|
114
|
+
long '--pidfile'
|
115
|
+
desc 'pidfile for when running as a daemon'
|
116
|
+
default 'mizuno.pid'
|
117
|
+
end
|
118
|
+
|
119
|
+
option :user do
|
120
|
+
long '--user'
|
121
|
+
desc 'user to run as'
|
122
|
+
default nil
|
123
|
+
end
|
124
|
+
|
125
|
+
option :group do
|
126
|
+
long '--group'
|
127
|
+
desc 'group to run as'
|
128
|
+
default nil
|
129
|
+
end
|
130
|
+
|
131
|
+
option :root do
|
132
|
+
long '--root'
|
133
|
+
desc 'app root (defaults to the current directory)'
|
134
|
+
default Dir.pwd
|
135
|
+
end
|
136
|
+
|
137
|
+
separator ''
|
138
|
+
separator 'Common options: '
|
139
|
+
|
140
|
+
option :help do
|
141
|
+
short '-h'
|
142
|
+
long '--help'
|
143
|
+
desc 'Show this message'
|
144
|
+
action { Choice.help }
|
145
|
+
end
|
146
|
+
|
147
|
+
option :version do
|
148
|
+
short '-v'
|
149
|
+
long '--version'
|
150
|
+
desc 'Show version'
|
151
|
+
action do
|
152
|
+
$stderr.puts Mizuno::HttpServer.version
|
153
|
+
exit
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
data/lib/mizuno/http_server.rb
CHANGED
@@ -1,15 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
"org.eclipse.jetty.util.log.StdErrLog")
|
1
|
+
require 'mizuno/version'
|
2
|
+
require 'mizuno/java_logger'
|
4
3
|
|
5
4
|
module Mizuno
|
6
5
|
class HttpServer
|
6
|
+
include_class 'java.util.Properties'
|
7
|
+
include_class 'java.io.ByteArrayInputStream'
|
8
|
+
include_class 'org.apache.log4j.PropertyConfigurator'
|
7
9
|
include_class 'org.eclipse.jetty.server.Server'
|
8
10
|
include_class 'org.eclipse.jetty.servlet.ServletContextHandler'
|
9
11
|
include_class 'org.eclipse.jetty.servlet.ServletHolder'
|
10
12
|
include_class 'org.eclipse.jetty.server.nio.SelectChannelConnector'
|
11
13
|
include_class 'org.eclipse.jetty.util.thread.QueuedThreadPool'
|
12
|
-
include_class 'org.eclipse.jetty.servlet.DefaultServlet'
|
14
|
+
# include_class 'org.eclipse.jetty.servlet.DefaultServlet'
|
15
|
+
# include_class 'org.eclipse.jetty.server.handler.HandlerCollection'
|
16
|
+
# include_class 'org.eclipse.jetty.server.handler.RequestLogHandler'
|
17
|
+
# include_class 'org.eclipse.jetty.server.NCSARequestLog'
|
13
18
|
|
14
19
|
#
|
15
20
|
# Provide accessors so we can set a custom logger and a location
|
@@ -32,19 +37,26 @@ module Mizuno
|
|
32
37
|
# String or integer with the port to bind to; defaults
|
33
38
|
# to 9292.
|
34
39
|
#
|
35
|
-
#
|
40
|
+
# http://wiki.eclipse.org/Jetty/Tutorial/RequestLog
|
36
41
|
#
|
37
|
-
|
42
|
+
# FIXME: Add SSL suport.
|
43
|
+
#
|
44
|
+
def HttpServer.run(app, options = {})
|
45
|
+
# Symbolize and downcase keys.
|
46
|
+
@options = options = Hash[options.map { |k, v|
|
47
|
+
[ k.to_s.downcase.to_sym, v ] }]
|
48
|
+
options[:quiet] ||= true if options[:embedded]
|
49
|
+
|
38
50
|
# The Jetty server
|
51
|
+
configure_logging(options)
|
39
52
|
@server = Server.new
|
40
|
-
|
41
|
-
options = Hash[options.map { |o|
|
42
|
-
[ o[0].to_s.downcase.to_sym, o[1] ] }]
|
53
|
+
@server.setSendServerVersion(false)
|
43
54
|
|
44
55
|
# Thread pool
|
56
|
+
threads = options[:threads] || 50
|
45
57
|
thread_pool = QueuedThreadPool.new
|
46
|
-
thread_pool.min_threads = 5
|
47
|
-
thread_pool.max_threads =
|
58
|
+
thread_pool.min_threads = [ threads.to_i / 10, 5 ].max
|
59
|
+
thread_pool.max_threads = [ threads.to_i, 10 ].max
|
48
60
|
@server.set_thread_pool(thread_pool)
|
49
61
|
|
50
62
|
# Connector
|
@@ -53,20 +65,36 @@ module Mizuno
|
|
53
65
|
connector.setHost(options[:host])
|
54
66
|
@server.addConnector(connector)
|
55
67
|
|
56
|
-
#
|
57
|
-
|
68
|
+
# Switch to a different user or group if we were asked to.
|
69
|
+
Runner.setgid(options) if options[:group]
|
70
|
+
Runner.setuid(options) if options[:user]
|
71
|
+
|
72
|
+
# Servlet handler.
|
73
|
+
app_handler = ServletContextHandler.new(nil, "/",
|
58
74
|
ServletContextHandler::NO_SESSIONS)
|
59
75
|
|
60
76
|
# The servlet itself.
|
61
77
|
rack_servlet = RackServlet.new
|
62
78
|
rack_servlet.rackup(app)
|
63
79
|
holder = ServletHolder.new(rack_servlet)
|
64
|
-
|
80
|
+
app_handler.addServlet(holder, "/")
|
81
|
+
|
82
|
+
# # Our request log.
|
83
|
+
# request_log = NCSARequestLog.new
|
84
|
+
# request_log.setLogTimeZone("GMT")
|
85
|
+
# request_log_handler = RequestLogHandler.new
|
86
|
+
# request_log_handler.setRequestLog(request_log)
|
87
|
+
#
|
88
|
+
# # Add handlers in order.
|
89
|
+
# handlers = HandlerCollection.new
|
90
|
+
# handlers.addHandler(request_log_handler)
|
91
|
+
# handlers.addHandler(app_handler)
|
65
92
|
|
66
93
|
# Add the context to the server and start.
|
67
|
-
@server.set_handler(
|
68
|
-
puts "Listening on #{connector.getHost}:#{connector.getPort}"
|
94
|
+
@server.set_handler(app_handler)
|
69
95
|
@server.start
|
96
|
+
$stderr.printf("%s listening on %s:%s\n", version,
|
97
|
+
connector.host, connector.port) unless options[:quiet]
|
70
98
|
|
71
99
|
# If we're embeded, we're done.
|
72
100
|
return if options[:embedded]
|
@@ -83,10 +111,66 @@ module Mizuno
|
|
83
111
|
#
|
84
112
|
# Shuts down an embedded Jetty instance.
|
85
113
|
#
|
86
|
-
def
|
114
|
+
def HttpServer.stop
|
87
115
|
return unless @server
|
88
|
-
|
116
|
+
$stderr.print "Stopping Jetty..." unless @options[:quiet]
|
89
117
|
@server.stop
|
118
|
+
$stderr.puts "done." unless @options[:quiet]
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Returns the full version string.
|
123
|
+
#
|
124
|
+
def HttpServer.version
|
125
|
+
"Mizuno #{Mizuno::VERSION} (Jetty #{Server.getVersion})"
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Configure Log4J.
|
130
|
+
#
|
131
|
+
def HttpServer.configure_logging(options)
|
132
|
+
return if @logger
|
133
|
+
|
134
|
+
# Default logging threshold.
|
135
|
+
limit = options[:warn] ? "WARN" : "ERROR"
|
136
|
+
limit = "DEBUG" if ($DEBUG or options[:debug])
|
137
|
+
|
138
|
+
# Base logging configuration.
|
139
|
+
config = <<-END
|
140
|
+
log4j.rootCategory = #{limit}, default
|
141
|
+
log4j.logger.org.eclipse.jetty.util.log = #{limit}, default
|
142
|
+
log4j.logger.ruby = INFO, ruby
|
143
|
+
log4j.appender.default.Threshold = #{limit}
|
144
|
+
log4j.appender.default.layout = org.apache.log4j.PatternLayout
|
145
|
+
log4j.appender.default.layout.ConversionPattern = %d %p %m
|
146
|
+
log4j.appender.ruby.Threshold = INFO
|
147
|
+
log4j.appender.ruby.layout = org.apache.log4j.PatternLayout
|
148
|
+
log4j.appender.ruby.layout.ConversionPattern = %m
|
149
|
+
END
|
150
|
+
|
151
|
+
# Should we log to the console?
|
152
|
+
config.concat(<<-END) unless options[:log]
|
153
|
+
log4j.appender.default = org.apache.log4j.ConsoleAppender
|
154
|
+
log4j.appender.ruby = org.apache.log4j.ConsoleAppender
|
155
|
+
END
|
156
|
+
|
157
|
+
# Are we logging to a file?
|
158
|
+
config.concat(<<-END) if options[:log]
|
159
|
+
log4j.appender.default = org.apache.log4j.FileAppender
|
160
|
+
log4j.appender.default.File = #{options[:log]}
|
161
|
+
log4j.appender.default.Append = true
|
162
|
+
log4j.appender.ruby = org.apache.log4j.FileAppender
|
163
|
+
log4j.appender.ruby.File = #{options[:log]}
|
164
|
+
log4j.appender.ruby.Append = true
|
165
|
+
END
|
166
|
+
|
167
|
+
# Set up Log4J via Properties.
|
168
|
+
properties = Properties.new
|
169
|
+
properties.load(ByteArrayInputStream.new(config.to_java_bytes))
|
170
|
+
PropertyConfigurator.configure(properties)
|
171
|
+
|
172
|
+
# Use log4j for our logging as well.
|
173
|
+
@logger = JavaLogger.new
|
90
174
|
end
|
91
175
|
end
|
92
176
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Mizuno
|
4
|
+
class JavaLogger < Logger
|
5
|
+
LEVELS = {
|
6
|
+
Logger::DEBUG => Java.org.apache.log4j.Level::DEBUG,
|
7
|
+
Logger::INFO => Java.org.apache.log4j.Level::INFO,
|
8
|
+
Logger::WARN => Java.org.apache.log4j.Level::WARN,
|
9
|
+
Logger::ERROR => Java.org.apache.log4j.Level::ERROR,
|
10
|
+
Logger::FATAL => Java.org.apache.log4j.Level::FATAL }
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@log4j = Java.org.apache.log4j.Logger.getLogger('ruby')
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(severity, message = nil, progname = nil)
|
17
|
+
content = (message or (block_given? and yield) or progname)
|
18
|
+
@log4j.log(LEVELS[severity], content)
|
19
|
+
end
|
20
|
+
|
21
|
+
def puts(message)
|
22
|
+
write(message.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(message)
|
26
|
+
add(INFO, message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def flush
|
30
|
+
# No-op.
|
31
|
+
end
|
32
|
+
|
33
|
+
def close
|
34
|
+
# No-op.
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
#
|
4
|
+
# We replace the default Rack::Chunked implementation with a non-op
|
5
|
+
# version, as Jetty handles chunking for us.
|
6
|
+
#
|
7
|
+
module Rack
|
8
|
+
class Chunked
|
9
|
+
include Rack::Utils
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/mizuno/rack_servlet.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'stringio'
|
2
|
+
require 'rack/response'
|
2
3
|
|
3
4
|
#
|
4
5
|
# Wraps a Rack application in a Java servlet.
|
@@ -15,6 +16,7 @@ module Mizuno
|
|
15
16
|
class RackServlet < HttpServlet
|
16
17
|
include_class java.io.FileInputStream
|
17
18
|
include_class org.eclipse.jetty.continuation.ContinuationSupport
|
19
|
+
include_class org.jruby.rack.servlet.RewindableInputStream
|
18
20
|
|
19
21
|
#
|
20
22
|
# Sets the Rack application that handles requests sent to this
|
@@ -40,47 +42,49 @@ module Mizuno
|
|
40
42
|
# empty body, we declare the async response finished.
|
41
43
|
#
|
42
44
|
def service(request, response)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
45
|
+
handle_exceptions(response) do
|
46
|
+
# Turn the ServletRequest into a Rack env hash
|
47
|
+
env = servlet_to_rack(request)
|
48
|
+
|
49
|
+
# Handle asynchronous responses via Servlet continuations.
|
50
|
+
continuation = ContinuationSupport.getContinuation(request)
|
51
|
+
|
52
|
+
# If this is an expired connection, do nothing.
|
53
|
+
return if continuation.isExpired
|
54
|
+
|
55
|
+
# We should never be re-dispatched.
|
56
|
+
raise("Request re-dispatched.") unless continuation.isInitial
|
57
|
+
|
58
|
+
# Add our own special bits to the rack environment so that
|
59
|
+
# Rack middleware can have access to the Java internals.
|
60
|
+
env['rack.java.servlet'] = true
|
61
|
+
env['rack.java.servlet.request'] = request
|
62
|
+
env['rack.java.servlet.response'] = response
|
63
|
+
env['rack.java.servlet.continuation'] = continuation
|
64
|
+
|
65
|
+
# Add an callback that can be used to add results to the
|
66
|
+
# response asynchronously.
|
67
|
+
env['async.callback'] = lambda do |rack_response|
|
68
|
+
servlet_response = continuation.getServletResponse
|
69
|
+
rack_to_servlet(rack_response, servlet_response) \
|
70
|
+
and continuation.complete
|
71
|
+
end
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
# Execute the Rack request.
|
74
|
+
catch(:async) do
|
75
|
+
rack_response = @app.call(env)
|
76
|
+
|
77
|
+
# For apps that don't throw :async.
|
78
|
+
unless(rack_response[0] == -1)
|
79
|
+
# Nope, nothing asynchronous here.
|
80
|
+
rack_to_servlet(rack_response, response)
|
81
|
+
return
|
82
|
+
end
|
79
83
|
end
|
80
|
-
end
|
81
84
|
|
82
|
-
|
83
|
-
|
85
|
+
# If we got here, this is a continuation.
|
86
|
+
continuation.suspend(response)
|
87
|
+
end
|
84
88
|
end
|
85
89
|
|
86
90
|
private
|
@@ -123,12 +127,12 @@ module Mizuno
|
|
123
127
|
env['rack.run_once'] = false
|
124
128
|
|
125
129
|
# The input stream is a wrapper around the Java InputStream.
|
126
|
-
env['rack.input'] =
|
130
|
+
env['rack.input'] = RewindableInputStream.new( \
|
131
|
+
request.getInputStream).to_io.binmode
|
127
132
|
|
128
133
|
# Force encoding if we're on Ruby 1.9
|
129
134
|
env['rack.input'].set_encoding(Encoding.find("ASCII-8BIT")) \
|
130
135
|
if env['rack.input'].respond_to?(:set_encoding)
|
131
|
-
# puts "**** rack.input.encoding: #{env['rack.input'].encoding}"
|
132
136
|
|
133
137
|
# Populate the HTTP headers.
|
134
138
|
request.getHeaderNames.each do |header_name|
|
@@ -143,8 +147,9 @@ module Mizuno
|
|
143
147
|
env["CONTENT_LENGTH"] = env.delete("HTTP_CONTENT_LENGTH") \
|
144
148
|
if env["HTTP_CONTENT_LENGTH"]
|
145
149
|
|
146
|
-
#
|
147
|
-
env['rack.errors'] ||=
|
150
|
+
# Route errors through the logger.
|
151
|
+
env['rack.errors'] ||= HttpServer.logger
|
152
|
+
env['rack.logger'] ||= HttpServer.logger
|
148
153
|
|
149
154
|
# All done, hand back the Rack request.
|
150
155
|
return(env)
|
@@ -179,7 +184,7 @@ module Mizuno
|
|
179
184
|
# data out on an async request.
|
180
185
|
unless(response.isCommitted)
|
181
186
|
# Set the HTTP status code.
|
182
|
-
response.setStatus(status)
|
187
|
+
response.setStatus(status.to_i)
|
183
188
|
|
184
189
|
# Did we get a Content-Length header?
|
185
190
|
content_length = headers.delete('Content-Length')
|
@@ -222,5 +227,22 @@ module Mizuno
|
|
222
227
|
# All done.
|
223
228
|
output.flush
|
224
229
|
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Handle exceptions, returning a generic 500 error response.
|
233
|
+
#
|
234
|
+
def handle_exceptions(response)
|
235
|
+
begin
|
236
|
+
yield
|
237
|
+
rescue => error
|
238
|
+
message = "Exception: #{error}"
|
239
|
+
message << "\n#{error.backtrace.join("\n")}" \
|
240
|
+
if (error.respond_to?(:backtrace))
|
241
|
+
HttpServer.logger.error(message)
|
242
|
+
return if response.isCommitted
|
243
|
+
response.reset
|
244
|
+
response.setStatus(500)
|
245
|
+
end
|
246
|
+
end
|
225
247
|
end
|
226
248
|
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'net/http'
|
3
|
+
require 'choice'
|
4
|
+
require 'mizuno/choices'
|
5
|
+
require 'childprocess'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'etc'
|
8
|
+
require 'rack'
|
9
|
+
|
10
|
+
module Mizuno
|
11
|
+
#
|
12
|
+
# Launches Mizuno when called from the command-line, and handles
|
13
|
+
# damonization via FFI.
|
14
|
+
#
|
15
|
+
# Daemonization code based on Spoon.
|
16
|
+
#
|
17
|
+
class Runner
|
18
|
+
extend FFI::Library
|
19
|
+
|
20
|
+
ffi_lib 'c'
|
21
|
+
|
22
|
+
attach_function :_setuid, :setuid, [ :uint ], :int
|
23
|
+
|
24
|
+
attach_function :_setgid, :setgid, [ :uint ], :int
|
25
|
+
|
26
|
+
#
|
27
|
+
# Switch the process over to a new user id; will abort the
|
28
|
+
# process if it fails. _options_ is the full list of options
|
29
|
+
# passed to a server.
|
30
|
+
#
|
31
|
+
def Runner.setuid(options)
|
32
|
+
entry = Etc.getpwnam(options[:user])
|
33
|
+
die("Can't find --user named '#{options[:user]}'") unless entry
|
34
|
+
return unless (_setuid(entry.uid) != 0)
|
35
|
+
die("Can't switch to user '#{options[:user]}'")
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Like setuid, but for groups.
|
40
|
+
#
|
41
|
+
def Runner.setgid(options)
|
42
|
+
entry = Etc.getgrnam(options[:group])
|
43
|
+
die("Can't find --group named '#{options[:group]}'") unless entry
|
44
|
+
return unless (_setgid(entry.gid) != 0)
|
45
|
+
die("Can't switch to group '#{options[:group]}'")
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Launch Jetty, optionally as a daemon.
|
50
|
+
#
|
51
|
+
def Runner.start!
|
52
|
+
# Default rackup is in config.ru
|
53
|
+
config = (Choice.rest.first or "config.ru")
|
54
|
+
|
55
|
+
# Create an options hash with only symbols.
|
56
|
+
choices = Choice.choices.merge(:config => config)
|
57
|
+
options = Hash[choices.map { |k, v| [ k.to_sym, v ] }]
|
58
|
+
|
59
|
+
# Resolve relative paths to the logfile, etc.
|
60
|
+
root = options[:root]
|
61
|
+
options[:pidfile] = Runner.resolve_path(root, options[:pidfile])
|
62
|
+
options[:log] = Runner.resolve_path(root, options[:log])
|
63
|
+
options[:public] = Runner.resolve_path(root, options[:public])
|
64
|
+
|
65
|
+
# Require multiple libraries.
|
66
|
+
options.delete(:require).each { |r| require r }
|
67
|
+
|
68
|
+
# Handle daemon-related commands.
|
69
|
+
Runner.status(options) if options.delete(:status)
|
70
|
+
Runner.reload(options) if options.delete(:reload)
|
71
|
+
Runner.stop(options) if options.delete(:stop)
|
72
|
+
Runner.kill(options) if options.delete(:kill)
|
73
|
+
Runner.daemonize(options) if options.delete(:daemonize)
|
74
|
+
|
75
|
+
# Fire up Mizuno as if it was called from Rackup.
|
76
|
+
Dir.chdir(options[:root])
|
77
|
+
HttpServer.configure_logging(options)
|
78
|
+
server = Rack::Server.new
|
79
|
+
server.options = options.merge(:server => 'mizuno',
|
80
|
+
:environment => options[:env])
|
81
|
+
server.start
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Relaunch as a daemon.
|
86
|
+
#
|
87
|
+
def Runner.daemonize(options)
|
88
|
+
# Ensure that Mizuno isn't running.
|
89
|
+
Runner.pid(options) and die("Mizuno is already running.")
|
90
|
+
|
91
|
+
# Build a command line that should launch JRuby with the
|
92
|
+
# appropriate options; this depends on the proper jruby
|
93
|
+
# being in the $PATH
|
94
|
+
config = options.delete(:config)
|
95
|
+
args = Mizuno::LAUNCH_ENV.concat(options.map { |k, v|
|
96
|
+
(v.to_s.empty?) ? nil : [ "--#{k}", v.to_s ] }.compact.flatten)
|
97
|
+
args.push(config)
|
98
|
+
args.unshift('jruby')
|
99
|
+
|
100
|
+
# Launch a detached child process.
|
101
|
+
child = ChildProcess.build(*args)
|
102
|
+
child.io.inherit!
|
103
|
+
child.detach = true
|
104
|
+
child.start
|
105
|
+
File.open(options[:pidfile], 'w') { |f| f.puts(child.pid) }
|
106
|
+
|
107
|
+
# Wait until the server starts or we time out waiting for it.
|
108
|
+
exit if wait_for_server(options)
|
109
|
+
child.stop
|
110
|
+
die("Failed to start Mizuno.")
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Return the status of a running daemon.
|
115
|
+
#
|
116
|
+
def Runner.status(options)
|
117
|
+
die("Mizuno doesn't appear to be running.") \
|
118
|
+
unless (pid = Runner.pid(options))
|
119
|
+
die("Mizuno is running, but not online.") \
|
120
|
+
unless(wait_for_server(options))
|
121
|
+
die("Mizuno is running.", true)
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Reload a running daemon by SIGHUPing it.
|
126
|
+
#
|
127
|
+
def Runner.reload(options)
|
128
|
+
pid = Runner.pid(options) or die("Mizuno isn't running.")
|
129
|
+
Process.kill("HUP", pid)
|
130
|
+
die("Mizuno signaled to reload app.", true)
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Stop a running daemon (SIGKILL)
|
135
|
+
#
|
136
|
+
def Runner.stop(options)
|
137
|
+
pid = Runner.pid(options) or die("Mizuno isn't running.")
|
138
|
+
print "Stopping Mizuno..."
|
139
|
+
Process.kill("KILL", pid)
|
140
|
+
die("failed") unless wait_for_server_to_die(options)
|
141
|
+
FileUtils.rm(options[:pidfile])
|
142
|
+
die("stopped", true)
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Really stop a running daemon (SIGTERM)
|
147
|
+
#
|
148
|
+
def Runner.kill(options)
|
149
|
+
pid = Runner.pid(options) or die("Mizuno isn't running.")
|
150
|
+
$stderr.puts "Terminating Mizuno with extreme prejudice..."
|
151
|
+
Process.kill("TERM", pid)
|
152
|
+
die("failed") unless wait_for_server_to_die(options)
|
153
|
+
FileUtils.rm(options[:pidfile])
|
154
|
+
die("stopped", true)
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# Transform a relative path to an absolute path.
|
159
|
+
#
|
160
|
+
def Runner.resolve_path(root, path)
|
161
|
+
return(path) unless path.is_a?(String)
|
162
|
+
return(path) if (path =~ /^\//)
|
163
|
+
File.expand_path(File.join(root, path))
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# Fetches the PID from the :pidfile.
|
168
|
+
#
|
169
|
+
def Runner.pid(options)
|
170
|
+
options[:pidfile] or die("Speficy a --pidfile to daemonize.")
|
171
|
+
return unless File.exists?(options[:pidfile])
|
172
|
+
pid = File.read(options[:pidfile]).to_i
|
173
|
+
|
174
|
+
# FIXME: This is a hacky way to get the process list, but I
|
175
|
+
# haven't found a good cross-platform solution yet; this
|
176
|
+
# should work on MacOS and Linux, possibly Solaris and BSD,
|
177
|
+
# and almost definitely not on Windows.
|
178
|
+
process = `ps ax`.lines.select { |l| l =~ /^\s*#{pid}\s*/ }
|
179
|
+
return(pid) if (process.join =~ /\bmizuno\b/)
|
180
|
+
|
181
|
+
# Stale pidfile; remove.
|
182
|
+
$stderr.puts("Removing stale pidfile '#{options[:pidfile]}'")
|
183
|
+
FileUtils.rm(options[:pidfile])
|
184
|
+
return(nil)
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Wait until _timeout_ seconds for a successful http connection;
|
189
|
+
# returns true if we could connect and didn't get a server
|
190
|
+
# error, false otherwise.
|
191
|
+
#
|
192
|
+
def Runner.wait_for_server(options, timeout = 10)
|
193
|
+
begin
|
194
|
+
Net::HTTP.start(options[:host], options[:port]) do |http|
|
195
|
+
http.read_timeout = timeout
|
196
|
+
response = http.get("/")
|
197
|
+
return(response.code.to_i < 500)
|
198
|
+
end
|
199
|
+
rescue Errno::ECONNREFUSED => error
|
200
|
+
return(false) unless ((timeout -= 0.5) > 0)
|
201
|
+
sleep(0.5)
|
202
|
+
retry
|
203
|
+
rescue => error
|
204
|
+
puts "HTTP Error '#{error}'"
|
205
|
+
return(false)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Like wait_for_server, but returns true when the server goes
|
211
|
+
# offline. If we hit _timeout_ seconds and the server is still
|
212
|
+
# responding, returns false.
|
213
|
+
#
|
214
|
+
def Runner.wait_for_server_to_die(options, timeout = 10)
|
215
|
+
begin
|
216
|
+
while(timeout > 0)
|
217
|
+
Net::HTTP.start(options[:host], options[:port]) do |http|
|
218
|
+
http.read_timeout = timeout
|
219
|
+
response = http.get("/")
|
220
|
+
puts "**** (die) response: #{response}"
|
221
|
+
end
|
222
|
+
timeout -= 0.5
|
223
|
+
sleep(0.5)
|
224
|
+
end
|
225
|
+
return(false)
|
226
|
+
rescue Errno::ECONNREFUSED => error
|
227
|
+
return(true)
|
228
|
+
rescue => error
|
229
|
+
puts "**** http error: #{error}"
|
230
|
+
return(true)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# Exit with a message and a status value.
|
236
|
+
#
|
237
|
+
# FIXME: Dump these in the logfile if called from HttpServer?
|
238
|
+
#
|
239
|
+
def Runner.die(message, success = false)
|
240
|
+
$stderr.puts(message)
|
241
|
+
exit(success ? 0 : 1)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
data/mizuno.gemspec
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
require 'lib/mizuno/version'
|
2
|
+
|
1
3
|
Gem::Specification.new do |spec|
|
2
4
|
spec.name = "mizuno"
|
3
|
-
spec.version =
|
5
|
+
spec.version = Mizuno::VERSION
|
4
6
|
spec.required_rubygems_version = Gem::Requirement.new(">= 1.2") \
|
5
7
|
if spec.respond_to?(:required_rubygems_version=)
|
6
8
|
spec.authors = [ "Don Werve" ]
|
7
9
|
spec.description = 'Jetty-powered running shoes for JRuby/Rack.'
|
8
|
-
spec.summary = 'Rack handler for Jetty
|
10
|
+
spec.summary = 'Rack handler for Jetty 8 on JRuby. Features multithreading, event-driven I/O, and async support.'
|
9
11
|
spec.email = 'don@madwombat.com'
|
10
|
-
# FIXME: We're not getting put in bin/
|
11
12
|
spec.executables = [ "mizuno" ]
|
13
|
+
# FIXME: Use Dir.glob for this
|
12
14
|
spec.files = %w(.gitignore
|
13
15
|
README.markdown
|
14
16
|
LICENSE
|
@@ -16,8 +18,13 @@ Gem::Specification.new do |spec|
|
|
16
18
|
Gemfile
|
17
19
|
mizuno.gemspec
|
18
20
|
tmp/.gitkeep
|
21
|
+
lib/mizuno/choices.rb
|
19
22
|
lib/mizuno/http_server.rb
|
23
|
+
lib/mizuno/java_logger.rb
|
24
|
+
lib/mizuno/rack/chunked.rb
|
20
25
|
lib/mizuno/rack_servlet.rb
|
26
|
+
lib/mizuno/runner.rb
|
27
|
+
lib/mizuno/version.rb
|
21
28
|
lib/mizuno.rb
|
22
29
|
bin/mizuno)
|
23
30
|
jars = Dir.entries("lib/java").grep(/\.jar$/)
|
@@ -27,8 +34,11 @@ Gem::Specification.new do |spec|
|
|
27
34
|
spec.require_paths = [ "lib" ]
|
28
35
|
spec.rubygems_version = '1.3.6'
|
29
36
|
spec.add_dependency('rack', '>= 1.0.0')
|
37
|
+
spec.add_dependency('ffi', '>= 1.0.0')
|
38
|
+
spec.add_dependency('choice', '>= 0.1.0')
|
39
|
+
spec.add_dependency('childprocess', '>= 0.2.6')
|
30
40
|
spec.add_development_dependency('rspec', '>= 2.7.0')
|
31
41
|
spec.add_development_dependency('rspec-core', '>= 2.7.0')
|
32
|
-
spec.add_development_dependency('json_pure', '>= 1.6.
|
42
|
+
spec.add_development_dependency('json_pure', '>= 1.6.0')
|
33
43
|
spec.add_development_dependency('nokogiri')
|
34
44
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: mizuno
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.5.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Don Werve
|
@@ -10,8 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date:
|
14
|
-
default_executable:
|
13
|
+
date: 2012-01-08 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: rack
|
@@ -25,49 +24,82 @@ dependencies:
|
|
25
24
|
type: :runtime
|
26
25
|
version_requirements: *id001
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
27
|
+
name: ffi
|
29
28
|
prerelease: false
|
30
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: choice
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.0
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: childprocess
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.2.6
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rspec
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
31
63
|
none: false
|
32
64
|
requirements:
|
33
65
|
- - ">="
|
34
66
|
- !ruby/object:Gem::Version
|
35
67
|
version: 2.7.0
|
36
68
|
type: :development
|
37
|
-
version_requirements: *
|
69
|
+
version_requirements: *id005
|
38
70
|
- !ruby/object:Gem::Dependency
|
39
71
|
name: rspec-core
|
40
72
|
prerelease: false
|
41
|
-
requirement: &
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
42
74
|
none: false
|
43
75
|
requirements:
|
44
76
|
- - ">="
|
45
77
|
- !ruby/object:Gem::Version
|
46
78
|
version: 2.7.0
|
47
79
|
type: :development
|
48
|
-
version_requirements: *
|
80
|
+
version_requirements: *id006
|
49
81
|
- !ruby/object:Gem::Dependency
|
50
82
|
name: json_pure
|
51
83
|
prerelease: false
|
52
|
-
requirement: &
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
53
85
|
none: false
|
54
86
|
requirements:
|
55
87
|
- - ">="
|
56
88
|
- !ruby/object:Gem::Version
|
57
|
-
version: 1.6.
|
89
|
+
version: 1.6.0
|
58
90
|
type: :development
|
59
|
-
version_requirements: *
|
91
|
+
version_requirements: *id007
|
60
92
|
- !ruby/object:Gem::Dependency
|
61
93
|
name: nokogiri
|
62
94
|
prerelease: false
|
63
|
-
requirement: &
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
64
96
|
none: false
|
65
97
|
requirements:
|
66
98
|
- - ">="
|
67
99
|
- !ruby/object:Gem::Version
|
68
100
|
version: "0"
|
69
101
|
type: :development
|
70
|
-
version_requirements: *
|
102
|
+
version_requirements: *id008
|
71
103
|
description: Jetty-powered running shoes for JRuby/Rack.
|
72
104
|
email: don@madwombat.com
|
73
105
|
executables:
|
@@ -84,8 +116,13 @@ files:
|
|
84
116
|
- Gemfile
|
85
117
|
- mizuno.gemspec
|
86
118
|
- tmp/.gitkeep
|
119
|
+
- lib/mizuno/choices.rb
|
87
120
|
- lib/mizuno/http_server.rb
|
121
|
+
- lib/mizuno/java_logger.rb
|
122
|
+
- lib/mizuno/rack/chunked.rb
|
88
123
|
- lib/mizuno/rack_servlet.rb
|
124
|
+
- lib/mizuno/runner.rb
|
125
|
+
- lib/mizuno/version.rb
|
89
126
|
- lib/mizuno.rb
|
90
127
|
- bin/mizuno
|
91
128
|
- lib/java/jetty-continuation-8.0.4.v20111024.jar
|
@@ -97,8 +134,11 @@ files:
|
|
97
134
|
- lib/java/jetty-servlet-8.0.4.v20111024.jar
|
98
135
|
- lib/java/jetty-servlets-8.0.4.v20111024.jar
|
99
136
|
- lib/java/jetty-util-8.0.4.v20111024.jar
|
137
|
+
- lib/java/log4j-1.2.16.jar
|
138
|
+
- lib/java/rewindable-input-stream.jar
|
100
139
|
- lib/java/servlet-api-3.0.jar
|
101
|
-
|
140
|
+
- lib/java/slf4j-api-1.6.4.jar
|
141
|
+
- lib/java/slf4j-log4j12-1.6.4.jar
|
102
142
|
homepage: http://github.com/matadon/mizuno
|
103
143
|
licenses: []
|
104
144
|
|
@@ -122,9 +162,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
162
|
requirements: []
|
123
163
|
|
124
164
|
rubyforge_project:
|
125
|
-
rubygems_version: 1.
|
165
|
+
rubygems_version: 1.8.9
|
126
166
|
signing_key:
|
127
167
|
specification_version: 3
|
128
|
-
summary: Rack handler for Jetty
|
168
|
+
summary: Rack handler for Jetty 8 on JRuby. Features multithreading, event-driven I/O, and async support.
|
129
169
|
test_files: []
|
130
170
|
|