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 ADDED
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ *.DS_Store
3
+ *.gem
4
+ Gemfile.lock
5
+ archive/
6
+ doc/
7
+ tmp/
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 'rack'
5
+ require 'rubygems'
6
6
  require 'mizuno'
7
+ require 'mizuno/runner'
7
8
 
8
- server = Rack::Server.new
9
- server.options[:server] = 'mizuno'
10
- server.start
9
+ Mizuno::Runner.start!
Binary file
Binary file
Binary file
data/lib/mizuno.rb CHANGED
@@ -1,16 +1,26 @@
1
1
  #
2
- # A Rack handler for Jetty 7.
2
+ # A Rack handler for Jetty 8.
3
3
  #
4
4
  # Written by Don Werve <don@madwombat.com>
5
5
  #
6
6
 
7
- # Java integration for talking to Jetty.
8
- require 'java'
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 Jetty JARs.
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
+
@@ -1,15 +1,20 @@
1
- # Have Jetty log to stdout for the time being.
2
- java.lang.System.setProperty("org.eclipse.jetty.util.log.class",
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
- # FIXME: Clean up options hash (all downcase, all symbols)
40
+ # http://wiki.eclipse.org/Jetty/Tutorial/RequestLog
36
41
  #
37
- def self.run(app, options = {})
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 = 50
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
- # Servlet context.
57
- context = ServletContextHandler.new(nil, "/",
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
- context.addServlet(holder, "/")
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(context)
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 self.stop
114
+ def HttpServer.stop
87
115
  return unless @server
88
- puts "Stopping Jetty..."
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
@@ -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
- # Turn the ServletRequest into a Rack env hash
44
- env = servlet_to_rack(request)
45
-
46
- # Handle asynchronous responses via Servlet continuations.
47
- continuation = ContinuationSupport.getContinuation(request)
48
-
49
- # If this is an expired connection, do nothing.
50
- return if continuation.isExpired
51
-
52
- # We should never be re-dispatched.
53
- raise("Request re-dispatched.") unless continuation.isInitial
54
-
55
- # Add our own special bits to the rack environment so that
56
- # Rack middleware can have access to the Java internals.
57
- env['rack.java.servlet'] = true
58
- env['rack.java.servlet.request'] = request
59
- env['rack.java.servlet.response'] = response
60
- env['rack.java.servlet.continuation'] = continuation
61
-
62
- # Add an callback that can be used to add results to the
63
- # response asynchronously.
64
- env['async.callback'] = lambda do |rack_response|
65
- servlet_response = continuation.getServletResponse
66
- rack_to_servlet(rack_response, servlet_response) \
67
- and continuation.complete
68
- end
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
- # Execute the Rack request.
71
- catch(:async) do
72
- rack_response = @app.call(env)
73
-
74
- # For apps that don't throw :async.
75
- unless(rack_response[0] == -1)
76
- # Nope, nothing asynchronous here.
77
- rack_to_servlet(rack_response, response)
78
- return
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
- # If we got here, this is a continuation.
83
- continuation.suspend(response)
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'] = request.getInputStream.to_io.binmode
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
- # The output stream defaults to stderr.
147
- env['rack.errors'] ||= $stderr
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
@@ -0,0 +1,3 @@
1
+ module Mizuno
2
+ VERSION = "0.5.0"
3
+ 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 = "0.4.1"
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 7 on JRuby. Features multithreading, event-driven I/O, and async support.'
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.1')
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.4.1
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: 2011-11-19 00:00:00 +09:00
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: rspec
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: *id002
69
+ version_requirements: *id005
38
70
  - !ruby/object:Gem::Dependency
39
71
  name: rspec-core
40
72
  prerelease: false
41
- requirement: &id003 !ruby/object:Gem::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: *id003
80
+ version_requirements: *id006
49
81
  - !ruby/object:Gem::Dependency
50
82
  name: json_pure
51
83
  prerelease: false
52
- requirement: &id004 !ruby/object:Gem::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.1
89
+ version: 1.6.0
58
90
  type: :development
59
- version_requirements: *id004
91
+ version_requirements: *id007
60
92
  - !ruby/object:Gem::Dependency
61
93
  name: nokogiri
62
94
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::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: *id005
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
- has_rdoc: true
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.5.1
165
+ rubygems_version: 1.8.9
126
166
  signing_key:
127
167
  specification_version: 3
128
- summary: Rack handler for Jetty 7 on JRuby. Features multithreading, event-driven I/O, and async support.
168
+ summary: Rack handler for Jetty 8 on JRuby. Features multithreading, event-driven I/O, and async support.
129
169
  test_files: []
130
170