mizuno 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -0,0 +1,4 @@
1
+ .gitignore
2
+ archive/
3
+ doc/
4
+ tmp/
@@ -27,7 +27,17 @@ because it doesn't produce WAR files or make any attempt to package a
27
27
  Rack application for installation in a Java web container.
28
28
 
29
29
  There's also a few features that I have yet to implement:
30
+
30
31
  1. Integrate the cometd servlet to provide Comet/Bayeux.
31
32
  2. Route Jetty's logs into Rack::Logger.
32
33
  3. Add hooks for realtime monitoring of server performance.
33
34
  4. Add the ability to run multiple Rack apps in a single JVM.
35
+
36
+ Mizuno is licensed under the Apache Public License, version 2.0; see
37
+ the LICENSE file for details, and was developed on behalf of
38
+ [Mad Wombat Software](http://www.madwombat.com)
39
+
40
+ Jetty is dual-licensed under the [Eclipse and Apache open-source
41
+ licenses](http://www.eclipse.org/jetty/licenses.php), and its
42
+ development is hosted by the [Eclipse
43
+ Foundation](http://www.eclipse.org/jetty/)
data/bin/mizuno CHANGED
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Make sure we're on JRuby.
4
- raise("Rack::Handler::Mizuno only runs on JRuby.") \
5
- unless (RUBY_PLATFORM =~ /java/)
3
+ raise("Mizuno only runs on JRuby.") unless (RUBY_PLATFORM =~ /java/)
6
4
 
7
5
  require 'rack'
8
- require 'rack/handler/mizuno'
6
+ require 'mizuno'
9
7
 
10
8
  server = Rack::Server.new
11
9
  server.options[:server] = 'mizuno'
Binary file
data/lib/mizuno.rb ADDED
@@ -0,0 +1,16 @@
1
+ #
2
+ # A Rack handler for Jetty 7.
3
+ #
4
+ # Written by Don Werve <don@madwombat.com>
5
+ #
6
+
7
+ # Java integration for talking to Jetty.
8
+ require 'java'
9
+
10
+ # Load Jetty JARs.
11
+ jars = File.join(File.dirname(__FILE__), 'java', '*.jar')
12
+ Dir[jars].each { |j| require j }
13
+
14
+ require 'rack'
15
+ require 'mizuno/rack_servlet'
16
+ require 'mizuno/http_server'
@@ -0,0 +1,90 @@
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")
4
+
5
+ module Mizuno
6
+ class HttpServer
7
+ include_class 'org.eclipse.jetty.server.Server'
8
+ include_class 'org.eclipse.jetty.servlet.ServletContextHandler'
9
+ include_class 'org.eclipse.jetty.servlet.ServletHolder'
10
+ include_class 'org.eclipse.jetty.server.nio.SelectChannelConnector'
11
+ include_class 'org.eclipse.jetty.util.thread.QueuedThreadPool'
12
+ include_class 'org.eclipse.jetty.servlet.DefaultServlet'
13
+
14
+ #
15
+ # Provide accessors so we can set a custom logger and a location
16
+ # for static assets.
17
+ #
18
+ class << self
19
+ attr_accessor :logger
20
+ end
21
+
22
+ #
23
+ # Start up an instance of Jetty, running a Rack application.
24
+ # Options can be any of the follwing, and are not
25
+ # case-sensitive:
26
+ #
27
+ # :host::
28
+ # String specifying the IP address to bind to; defaults
29
+ # to 0.0.0.0.
30
+ #
31
+ # :port::
32
+ # String or integer with the port to bind to; defaults
33
+ # to 9292.
34
+ #
35
+ # FIXME: Clean up options hash (all downcase, all symbols)
36
+ #
37
+ def self.run(app, options = {})
38
+ # The Jetty server
39
+ @server = Server.new
40
+
41
+ options = Hash[options.map { |o|
42
+ [ o[0].to_s.downcase.to_sym, o[1] ] }]
43
+
44
+ # Thread pool
45
+ thread_pool = QueuedThreadPool.new
46
+ thread_pool.min_threads = 5
47
+ thread_pool.max_threads = 50
48
+ @server.set_thread_pool(thread_pool)
49
+
50
+ # Connector
51
+ connector = SelectChannelConnector.new
52
+ connector.setPort(options[:port].to_i)
53
+ connector.setHost(options[:host])
54
+ @server.addConnector(connector)
55
+
56
+ # Servlet context.
57
+ context = ServletContextHandler.new(nil, "/",
58
+ ServletContextHandler::NO_SESSIONS)
59
+
60
+ # The servlet itself.
61
+ rack_servlet = RackServlet.new
62
+ rack_servlet.rackup(app)
63
+ holder = ServletHolder.new(rack_servlet)
64
+ context.addServlet(holder, "/")
65
+
66
+ # Add the context to the server and start.
67
+ @server.set_handler(context)
68
+ puts "Listening on #{connector.getHost}:#{connector.getPort}"
69
+ @server.start
70
+
71
+ # Stop the server when we get The Signal.
72
+ trap("SIGINT") { @server.stop and exit }
73
+
74
+ # Join with the server thread, so that currently open file
75
+ # descriptors don't get closed by accident.
76
+ # http://www.ruby-forum.com/topic/209252
77
+ @server.join unless options[:embedded]
78
+ end
79
+
80
+ #
81
+ # Shuts down an embedded Jetty instance.
82
+ #
83
+ def self.stop
84
+ @server.stop
85
+ end
86
+ end
87
+ end
88
+
89
+ # Register ourselves with Rack when this file gets loaded.
90
+ Rack::Handler.register 'mizuno', 'Mizuno::HttpServer'
@@ -0,0 +1,219 @@
1
+ #
2
+ # Wraps a Rack application in a Java servlet.
3
+ #
4
+ # Relevant documentation:
5
+ #
6
+ # http://rack.rubyforge.org/doc/SPEC.html
7
+ # http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax
8
+ # /servlet/http/HttpServlet.html
9
+ #
10
+ module Mizuno
11
+ include_class javax.servlet.http.HttpServlet
12
+
13
+ class RackServlet < HttpServlet
14
+ include_class java.io.FileInputStream
15
+ include_class org.eclipse.jetty.continuation.ContinuationSupport
16
+
17
+ #
18
+ # Sets the Rack application that handles requests sent to this
19
+ # servlet container.
20
+ #
21
+ def rackup(app)
22
+ @app = app
23
+ end
24
+
25
+ #
26
+ # Takes an incoming request (as a Java Servlet) and dispatches it to
27
+ # the rack application setup via [rackup]. All this really involves
28
+ # is translating the various bits of the Servlet API into the Rack
29
+ # API on the way in, and translating the response back on the way
30
+ # out.
31
+ #
32
+ # Also, we implement a common extension to the Rack api for
33
+ # asynchronous request processing. We supply an 'async.callback'
34
+ # parameter in env to the Rack application. If we catch an
35
+ # :async symbol thrown by the app, we initiate a Jetty continuation.
36
+ #
37
+ # When 'async.callback' gets a response with empty headers and an
38
+ # empty body, we declare the async response finished.
39
+ #
40
+ def service(request, response)
41
+ # Turn the ServletRequest into a Rack env hash
42
+ env = servlet_to_rack(request)
43
+
44
+ # Handle asynchronous responses via Servlet continuations.
45
+ continuation = ContinuationSupport.getContinuation(request)
46
+
47
+ # If this is an expired connection, do nothing.
48
+ return if continuation.isExpired
49
+
50
+ # We should never be re-dispatched.
51
+ raise("Request re-dispatched.") unless continuation.isInitial
52
+
53
+ # Add our own special bits to the rack environment so that
54
+ # Rack middleware can have access to the Java internals.
55
+ env['rack.java.servlet'] = true
56
+ env['rack.java.servlet.request'] = request
57
+ env['rack.java.servlet.response'] = response
58
+ env['rack.java.servlet.continuation'] = continuation
59
+
60
+ # Add an callback that can be used to add results to the
61
+ # response asynchronously.
62
+ env['async.callback'] = lambda do |rack_response|
63
+ servlet_response = continuation.getServletResponse
64
+ rack_to_servlet(rack_response, servlet_response) \
65
+ and continuation.complete
66
+ end
67
+
68
+ # Execute the Rack request.
69
+ catch(:async) do
70
+ rack_response = @app.call(env)
71
+
72
+ # For apps that don't throw :async.
73
+ unless(rack_response[0] == -1)
74
+ # Nope, nothing asynchronous here.
75
+ rack_to_servlet(rack_response, response)
76
+ return
77
+ end
78
+ end
79
+
80
+ # If we got here, this is a continuation.
81
+ continuation.suspend(response)
82
+ end
83
+
84
+ private
85
+
86
+ #
87
+ # Turns a Servlet request into a Rack request hash.
88
+ #
89
+ def servlet_to_rack(request)
90
+ # The Rack request that we will pass on.
91
+ env = Hash.new
92
+
93
+ # Map Servlet bits to Rack bits.
94
+ env['REQUEST_METHOD'] = request.getMethod
95
+ env['QUERY_STRING'] = request.getQueryString.to_s
96
+ env['SERVER_NAME'] = request.getServerName
97
+ env['SERVER_PORT'] = request.getServerPort.to_s
98
+ env['rack.version'] = Rack::VERSION
99
+ env['rack.url_scheme'] = request.getScheme
100
+ env['HTTP_VERSION'] = request.getProtocol
101
+ env["SERVER_PROTOCOL"] = request.getProtocol
102
+ env['REMOTE_ADDR'] = request.getRemoteAddr
103
+ env['REMOTE_HOST'] = request.getRemoteHost
104
+
105
+ # request.getPathInfo seems to be blank, so we're using the URI.
106
+ env['REQUEST_PATH'] = request.getRequestURI
107
+ env['PATH_INFO'] = request.getRequestURI
108
+ env['SCRIPT_NAME'] = ""
109
+
110
+ # Rack says URI, but it hands off a URL.
111
+ env['REQUEST_URI'] = request.getRequestURL.toString
112
+
113
+ # Java chops off the query string, but a Rack application will
114
+ # expect it, so we'll add it back if present
115
+ env['REQUEST_URI'] << "?#{env['QUERY_STRING']}" \
116
+ if env['QUERY_STRING']
117
+
118
+ # JRuby is like the matrix, only there's no spoon or fork().
119
+ env['rack.multiprocess'] = false
120
+ env['rack.multithread'] = true
121
+ env['rack.run_once'] = false
122
+
123
+ # Populate the HTTP headers.
124
+ request.getHeaderNames.each do |header_name|
125
+ header = header_name.upcase.tr('-', '_')
126
+ env["HTTP_#{header}"] = request.getHeader(header_name)
127
+ end
128
+
129
+ # Rack Weirdness: HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH
130
+ # both need to have the HTTP_ part dropped.
131
+ env["CONTENT_TYPE"] = env.delete("HTTP_CONTENT_TYPE") \
132
+ if env["HTTP_CONTENT_TYPE"]
133
+ env["CONTENT_LENGTH"] = env.delete("HTTP_CONTENT_LENGTH") \
134
+ if env["HTTP_CONTENT_LENGTH"]
135
+
136
+ # The input stream is a wrapper around the Java InputStream.
137
+ env['rack.input'] = request.getInputStream.to_io
138
+
139
+ # The output stream defaults to stderr.
140
+ env['rack.errors'] ||= $stderr
141
+
142
+ # All done, hand back the Rack request.
143
+ return(env)
144
+ end
145
+
146
+ #
147
+ # Turns a Rack response into a Servlet response; can be called
148
+ # multiple times. Returns true if this is the full request (either
149
+ # a synchronous request or the last part of an async request),
150
+ # false otherwise.
151
+ #
152
+ # Note that keep-alive *only* happens if we get either a pathname
153
+ # (because we can find the length ourselves), or if we get a
154
+ # Content-Length header as part of the response. While we can
155
+ # readily buffer the response object to figure out how long it is,
156
+ # we have no guarantee that we aren't going to be buffering
157
+ # something *huge*.
158
+ #
159
+ # http://docstore.mik.ua/orelly/java-ent/servlet/ch05_03.htm
160
+ #
161
+ def rack_to_servlet(rack_response, response)
162
+ # Split apart the Rack response.
163
+ status, headers, body = rack_response
164
+
165
+ # We assume the request is finished if we got empty headers,
166
+ # an empty body, and we have a committed response.
167
+ finished = (headers.empty? and \
168
+ body.respond_to?(:empty?) and body.empty?)
169
+ return(true) if (finished and response.isCommitted)
170
+
171
+ # No need to send headers again if we've already shipped
172
+ # data out on an async request.
173
+ unless(response.isCommitted)
174
+ # Set the HTTP status code.
175
+ response.setStatus(status)
176
+
177
+ # Did we get a Content-Length header?
178
+ content_length = headers.delete('Content-Length')
179
+ response.setContentLength(content_length.to_i) \
180
+ if content_length
181
+
182
+ # Add all the result headers.
183
+ headers.each { |h, v| response.addHeader(h, v) }
184
+ end
185
+
186
+ # How else would we write output?
187
+ output = response.getOutputStream
188
+
189
+ # Turn the body into something nice and Java-y.
190
+ if(body.respond_to?(:to_path))
191
+ # We've been told to serve a file; use FileInputStream to
192
+ # stream the file directly to the servlet, because this
193
+ # is a lot faster than doing it with Ruby.
194
+ file = java.io.File.new(body.to_path)
195
+
196
+ # We set the content-length so we can use Keep-Alive,
197
+ # unless this is an async request.
198
+ response.setContentLength(file.length) \
199
+ unless content_length
200
+
201
+ # Stream the file directly.
202
+ buffer = Java::byte[4096].new
203
+ input_stream = FileInputStream.new(file)
204
+ while((count = input_stream.read(buffer)) != -1)
205
+ output.write(buffer, 0, count)
206
+ end
207
+ input_stream.close
208
+ else
209
+ body.each { |l| output.write(l.to_java_bytes) }
210
+ end
211
+
212
+ # Close the body if we're supposed to.
213
+ body.close if body.respond_to?(:close)
214
+
215
+ # All done.
216
+ output.flush
217
+ end
218
+ end
219
+ end
data/mizuno.gemspec CHANGED
@@ -1,35 +1,34 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "mizuno"
3
- spec.version = "0.3.6"
3
+ spec.version = "0.4.0"
4
4
  spec.required_rubygems_version = Gem::Requirement.new(">= 1.2") \
5
- if spec.respond_to?(:required_rubygems_version=)
5
+ if spec.respond_to?(:required_rubygems_version=)
6
6
  spec.authors = [ "Don Werve" ]
7
7
  spec.description = 'Jetty-powered running shoes for JRuby/Rack.'
8
8
  spec.summary = 'Rack handler for Jetty 7 on JRuby. Features multithreading, event-driven I/O, and async support.'
9
9
  spec.email = 'don@madwombat.com'
10
10
  spec.executables = [ "mizuno" ]
11
- spec.files = [ ".gitignore",
12
- "README",
13
- "LICENSE",
14
- "mizuno.gemspec",
15
- "lib/java/cometd-api-1.1.0.jar",
16
- "lib/java/cometd-java-server-1.1.0.jar",
17
- "lib/java/jetty-continuation-7.0.2.v20100331.jar",
18
- "lib/java/jetty-http-7.0.2.v20100331.jar",
19
- "lib/java/jetty-io-7.0.2.v20100331.jar",
20
- "lib/java/jetty-jmx-7.0.2.v20100331.jar",
21
- "lib/java/jetty-security-7.0.2.v20100331.jar",
22
- "lib/java/jetty-server-7.0.2.v20100331.jar",
23
- "lib/java/jetty-servlet-7.0.2.v20100331.jar",
24
- "lib/java/jetty-servlets-7.0.2.v20100331.jar",
25
- "lib/java/jetty-util-7.0.2.v20100331.jar",
26
- "lib/java/servlet-api-2.5.jar",
27
- "lib/rack/handler/mizuno/http_server.rb",
28
- "lib/rack/handler/mizuno/rack_servlet.rb",
29
- "lib/rack/handler/mizuno.rb",
30
- "bin/mizuno" ]
11
+ spec.files = %w( .gitignore
12
+ README.markdown
13
+ LICENSE
14
+ mizuno.gemspec
15
+ lib/java/jetty-continuation-7.3.0.v20110203.jar
16
+ lib/java/jetty-http-7.3.0.v20110203.jar
17
+ lib/java/jetty-io-7.3.0.v20110203.jar
18
+ lib/java/jetty-jmx-7.3.0.v20110203.jar
19
+ lib/java/jetty-security-7.3.0.v20110203.jar
20
+ lib/java/jetty-server-7.3.0.v20110203.jar
21
+ lib/java/jetty-servlet-7.3.0.v20110203.jar
22
+ lib/java/jetty-servlets-7.3.0.v20110203.jar
23
+ lib/java/jetty-util-7.3.0.v20110203.jar
24
+ lib/java/servlet-api-2.5.jar
25
+ lib/mizuno/http_server.rb
26
+ lib/mizuno/rack_servlet.rb
27
+ lib/mizuno.rb
28
+ bin/mizuno )
31
29
  spec.homepage = 'http://github.com/matadon/mizuno'
32
30
  spec.has_rdoc = false
33
31
  spec.require_paths = [ "lib" ]
34
32
  spec.rubygems_version = '1.3.6'
33
+ spec.add_dependency('rack', '>= 1.0.0')
35
34
  end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mizuno
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 3
8
- - 6
9
- version: 0.3.6
4
+ prerelease:
5
+ version: 0.4.0
10
6
  platform: ruby
11
7
  authors:
12
8
  - Don Werve
@@ -14,10 +10,20 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2010-07-03 00:00:00 +09:00
13
+ date: 2011-02-13 00:00:00 +09:00
18
14
  default_executable:
19
- dependencies: []
20
-
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rack
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.0.0
25
+ type: :runtime
26
+ version_requirements: *id001
21
27
  description: Jetty-powered running shoes for JRuby/Rack.
22
28
  email: don@madwombat.com
23
29
  executables:
@@ -28,24 +34,22 @@ extra_rdoc_files: []
28
34
 
29
35
  files:
30
36
  - .gitignore
31
- - README
37
+ - README.markdown
32
38
  - LICENSE
33
39
  - mizuno.gemspec
34
- - lib/java/cometd-api-1.1.0.jar
35
- - lib/java/cometd-java-server-1.1.0.jar
36
- - lib/java/jetty-continuation-7.0.2.v20100331.jar
37
- - lib/java/jetty-http-7.0.2.v20100331.jar
38
- - lib/java/jetty-io-7.0.2.v20100331.jar
39
- - lib/java/jetty-jmx-7.0.2.v20100331.jar
40
- - lib/java/jetty-security-7.0.2.v20100331.jar
41
- - lib/java/jetty-server-7.0.2.v20100331.jar
42
- - lib/java/jetty-servlet-7.0.2.v20100331.jar
43
- - lib/java/jetty-servlets-7.0.2.v20100331.jar
44
- - lib/java/jetty-util-7.0.2.v20100331.jar
40
+ - lib/java/jetty-continuation-7.3.0.v20110203.jar
41
+ - lib/java/jetty-http-7.3.0.v20110203.jar
42
+ - lib/java/jetty-io-7.3.0.v20110203.jar
43
+ - lib/java/jetty-jmx-7.3.0.v20110203.jar
44
+ - lib/java/jetty-security-7.3.0.v20110203.jar
45
+ - lib/java/jetty-server-7.3.0.v20110203.jar
46
+ - lib/java/jetty-servlet-7.3.0.v20110203.jar
47
+ - lib/java/jetty-servlets-7.3.0.v20110203.jar
48
+ - lib/java/jetty-util-7.3.0.v20110203.jar
45
49
  - lib/java/servlet-api-2.5.jar
46
- - lib/rack/handler/mizuno/http_server.rb
47
- - lib/rack/handler/mizuno/rack_servlet.rb
48
- - lib/rack/handler/mizuno.rb
50
+ - lib/mizuno/http_server.rb
51
+ - lib/mizuno/rack_servlet.rb
52
+ - lib/mizuno.rb
49
53
  - bin/mizuno
50
54
  has_rdoc: true
51
55
  homepage: http://github.com/matadon/mizuno
@@ -61,22 +65,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
65
  requirements:
62
66
  - - ">="
63
67
  - !ruby/object:Gem::Version
64
- segments:
65
- - 0
66
68
  version: "0"
67
69
  required_rubygems_version: !ruby/object:Gem::Requirement
68
70
  none: false
69
71
  requirements:
70
72
  - - ">="
71
73
  - !ruby/object:Gem::Version
72
- segments:
73
- - 1
74
- - 2
75
74
  version: "1.2"
76
75
  requirements: []
77
76
 
78
77
  rubyforge_project:
79
- rubygems_version: 1.3.7
78
+ rubygems_version: 1.4.2
80
79
  signing_key:
81
80
  specification_version: 3
82
81
  summary: Rack handler for Jetty 7 on JRuby. Features multithreading, event-driven I/O, and async support.
Binary file
Binary file
Binary file
Binary file
@@ -1 +0,0 @@
1
- require 'rack/handler/mizuno/http_server'
@@ -1,64 +0,0 @@
1
- #
2
- # A Rack handler for Jetty 7.
3
- #
4
- # Written by Don Werve <don@madwombat.com>
5
- #
6
-
7
- require 'java'
8
- require 'rack'
9
-
10
- # Load Jetty JARs.
11
- jars = File.join(File.dirname(__FILE__), '..', '..', '..', 'java', '*.jar')
12
- Dir[jars].each { |j| require j }
13
-
14
- # Load the Rack/Servlet bridge.
15
- require 'rack/handler/mizuno/rack_servlet'
16
-
17
- # Have Jetty log to stdout for the time being.
18
- java.lang.System.setProperty("org.eclipse.jetty.util.log.class",
19
- "org.eclipse.jetty.util.log.StdErrLog")
20
-
21
- module Rack::Handler::Mizuno
22
- class HttpServer
23
- include_class 'org.eclipse.jetty.server.Server'
24
- include_class 'org.eclipse.jetty.servlet.ServletContextHandler'
25
- include_class 'org.eclipse.jetty.servlet.ServletHolder'
26
- include_class 'org.eclipse.jetty.server.nio.SelectChannelConnector'
27
- include_class 'org.eclipse.jetty.util.thread.QueuedThreadPool'
28
-
29
- def self.run(app, options = {})
30
- # The Jetty server
31
- server = Server.new
32
-
33
- # Thread pool
34
- thread_pool = QueuedThreadPool.new
35
- thread_pool.min_threads = 5
36
- thread_pool.max_threads = 50
37
- server.set_thread_pool(thread_pool)
38
-
39
- # Connector
40
- connector = SelectChannelConnector.new
41
- connector.setPort(options[:Port].to_i)
42
- connector.setHost(options[:Host])
43
- server.addConnector(connector)
44
-
45
- # Servlet context.
46
- context = ServletContextHandler.new(nil, "/",
47
- ServletContextHandler::NO_SESSIONS)
48
-
49
- # The servlet itself.
50
- servlet = RackServlet.new
51
- servlet.rackup(app)
52
- holder = ServletHolder.new(servlet)
53
- context.addServlet(holder, "/")
54
-
55
- # Add the context to the server and start.
56
- server.set_handler(context)
57
- puts "Started Jetty on #{connector.getHost}:#{connector.getPort}"
58
- server.start
59
- end
60
- end
61
- end
62
-
63
- # Register ourselves with Rack when this file gets loaded.
64
- Rack::Handler.register 'mizuno', 'Rack::Handler::Mizuno::HttpServer'
@@ -1,218 +0,0 @@
1
- #
2
- # Wraps a Rack application in a Java servlet.
3
- #
4
- # Relevant documentation:
5
- #
6
- # http://rack.rubyforge.org/doc/SPEC.html
7
- # http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServlet.html
8
- #
9
- module Rack::Handler::Mizuno
10
- include_class javax.servlet.http.HttpServlet
11
-
12
- class RackServlet < HttpServlet
13
- include_class java.io.FileInputStream
14
- include_class org.eclipse.jetty.continuation.ContinuationSupport
15
-
16
- #
17
- # Sets the Rack application that handles requests sent to this
18
- # servlet container.
19
- #
20
- def rackup(app)
21
- @app = app
22
- end
23
-
24
- #
25
- # Takes an incoming request (as a Java Servlet) and dispatches it to
26
- # the rack application setup via [rackup]. All this really involves
27
- # is translating the various bits of the Servlet API into the Rack
28
- # API on the way in, and translating the response back on the way
29
- # out.
30
- #
31
- # Also, we implement a common extension to the Rack api for
32
- # asynchronous request processing. We supply an 'async.callback'
33
- # parameter in env to the Rack application. If we catch an
34
- # :async symbol thrown by the app, we initiate a Jetty continuation.
35
- #
36
- # When 'async.callback' gets a response with empty headers and an
37
- # empty body, we declare the async response finished.
38
- #
39
- def service(request, response)
40
- # Turn the ServletRequest into a Rack env hash
41
- env = servlet_to_rack(request)
42
-
43
- # Handle asynchronous responses via Servlet continuations.
44
- continuation = ContinuationSupport.getContinuation(request)
45
-
46
- # If this is an expired connection, do nothing.
47
- return if continuation.isExpired
48
-
49
- # We should never be re-dispatched.
50
- raise("Request re-dispatched.") unless continuation.isInitial
51
-
52
- # Add our own special bits to the rack environment so that
53
- # Rack middleware can have access to the Java internals.
54
- env['rack.java.servlet'] = true
55
- env['rack.java.servlet.request'] = request
56
- env['rack.java.servlet.response'] = response
57
- env['rack.java.servlet.continuation'] = continuation
58
-
59
- # Add an callback that can be used to add results to the
60
- # response asynchronously.
61
- env['async.callback'] = lambda do |rack_response|
62
- servlet_response = continuation.getServletResponse
63
- rack_to_servlet(rack_response, servlet_response) \
64
- and continuation.complete
65
- end
66
-
67
- # Execute the Rack request.
68
- catch(:async) do
69
- rack_response = @app.call(env)
70
-
71
- # For apps that don't throw :async.
72
- unless(rack_response[0] == -1)
73
- # Nope, nothing asynchronous here.
74
- rack_to_servlet(rack_response, response)
75
- return
76
- end
77
- end
78
-
79
- # If we got here, this is a continuation.
80
- continuation.suspend(response)
81
- end
82
-
83
- private
84
-
85
- #
86
- # Turns a Servlet request into a Rack request hash.
87
- #
88
- def servlet_to_rack(request)
89
- # The Rack request that we will pass on.
90
- env = Hash.new
91
-
92
- # Map Servlet bits to Rack bits.
93
- env['REQUEST_METHOD'] = request.getMethod
94
- env['QUERY_STRING'] = request.getQueryString.to_s
95
- env['SERVER_NAME'] = request.getServerName
96
- env['SERVER_PORT'] = request.getServerPort.to_s
97
- env['rack.version'] = Rack::VERSION
98
- env['rack.url_scheme'] = request.getScheme
99
- env['HTTP_VERSION'] = request.getProtocol
100
- env["SERVER_PROTOCOL"] = request.getProtocol
101
- env['REMOTE_ADDR'] = request.getRemoteAddr
102
- env['REMOTE_HOST'] = request.getRemoteHost
103
-
104
- # request.getPathInfo seems to be blank, so we're using the URI.
105
- env['REQUEST_PATH'] = request.getRequestURI
106
- env['PATH_INFO'] = request.getRequestURI
107
- env['SCRIPT_NAME'] = ""
108
-
109
- # Rack says URI, but it hands off a URL.
110
- env['REQUEST_URI'] = request.getRequestURL.toString
111
-
112
- # Java chops off the query string, but a Rack application will
113
- # expect it, so we'll add it back if present
114
- env['REQUEST_URI'] << "?#{env['QUERY_STRING']}" \
115
- if env['QUERY_STRING']
116
-
117
- # JRuby is like the matrix, only there's no spoon or fork().
118
- env['rack.multiprocess'] = false
119
- env['rack.multithread'] = true
120
- env['rack.run_once'] = false
121
-
122
- # Populate the HTTP headers.
123
- request.getHeaderNames.each do |header_name|
124
- header = header_name.upcase.tr('-', '_')
125
- env["HTTP_#{header}"] = request.getHeader(header_name)
126
- end
127
-
128
- # Rack Weirdness: HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH
129
- # both need to have the HTTP_ part dropped.
130
- env["CONTENT_TYPE"] = env.delete("HTTP_CONTENT_TYPE") \
131
- if env["HTTP_CONTENT_TYPE"]
132
- env["CONTENT_LENGTH"] = env.delete("HTTP_CONTENT_LENGTH") \
133
- if env["HTTP_CONTENT_LENGTH"]
134
-
135
- # The input stream is a wrapper around the Java InputStream.
136
- env['rack.input'] = request.getInputStream.to_io
137
-
138
- # The output stream defaults to stderr.
139
- env['rack.errors'] ||= $stderr
140
-
141
- # All done, hand back the Rack request.
142
- return(env)
143
- end
144
-
145
- #
146
- # Turns a Rack response into a Servlet response; can be called
147
- # multiple times. Returns true if this is the full request (either
148
- # a synchronous request or the last part of an async request),
149
- # false otherwise.
150
- #
151
- # Note that keep-alive *only* happens if we get either a pathname
152
- # (because we can find the length ourselves), or if we get a
153
- # Content-Length header as part of the response. While we can
154
- # readily buffer the response object to figure out how long it is,
155
- # we have no guarantee that we aren't going to be buffering
156
- # something *huge*.
157
- #
158
- # http://docstore.mik.ua/orelly/java-ent/servlet/ch05_03.htm
159
- #
160
- def rack_to_servlet(rack_response, response)
161
- # Split apart the Rack response.
162
- status, headers, body = rack_response
163
-
164
- # We assume the request is finished if we got empty headers,
165
- # an empty body, and we have a committed response.
166
- finished = (headers.empty? and \
167
- body.respond_to?(:empty?) and body.empty?)
168
- return(true) if (finished and response.isCommitted)
169
-
170
- # No need to send headers again if we've already shipped
171
- # data out on an async request.
172
- unless(response.isCommitted)
173
- # Set the HTTP status code.
174
- response.setStatus(status)
175
-
176
- # Did we get a Content-Length header?
177
- content_length = headers.delete('Content-Length')
178
- response.setContentLength(content_length.to_i) \
179
- if content_length
180
-
181
- # Add all the result headers.
182
- headers.each { |h, v| response.addHeader(h, v) }
183
- end
184
-
185
- # How else would we write output?
186
- output = response.getOutputStream
187
-
188
- # Turn the body into something nice and Java-y.
189
- if(body.respond_to?(:to_path))
190
- # We've been told to serve a file; use FileInputStream to
191
- # stream the file directly to the servlet, because this
192
- # is a lot faster than doing it with Ruby.
193
- file = java.io.File.new(body.to_path)
194
-
195
- # We set the content-length so we can use Keep-Alive,
196
- # unless this is an async request.
197
- response.setContentLength(file.length) \
198
- unless content_length
199
-
200
- # Stream the file directly.
201
- buffer = Java::byte[4096].new
202
- input_stream = FileInputStream.new(file)
203
- while((count = input_stream.read(buffer)) != -1)
204
- output.write(buffer, 0, count)
205
- end
206
- input_stream.close
207
- else
208
- body.each { |l| output.write(l.to_java_bytes) }
209
- end
210
-
211
- # Close the body if we're supposed to.
212
- body.close if body.respond_to?(:close)
213
-
214
- # All done.
215
- output.flush
216
- end
217
- end
218
- end