mizuno 0.4.1 → 0.5.0

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/.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