mizuno-aspace 9.4.44

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ require 'stringio'
2
+ require 'mizuno/client_response'
3
+
4
+ module Mizuno
5
+ java_import 'org.eclipse.jetty.client.ContentExchange'
6
+
7
+ # what do I want to happen on a timeout or error?
8
+
9
+ class ClientExchange < ContentExchange
10
+ def initialize(client)
11
+ super(false)
12
+ @client = client
13
+ end
14
+
15
+ def setup(url, options = {}, &block)
16
+ @callback = block
17
+ @response = ClientResponse.new(url)
18
+ setURL(url)
19
+ @response.ssl = (getScheme == 'https')
20
+ setMethod((options[:method] or "GET").upcase)
21
+ headers = options[:headers] and headers.each_pair { |k, v|
22
+ setRequestHeader(k, v) }
23
+ return unless options[:body]
24
+ body = StringIO.new(options[:body].read)
25
+ setRequestContentSource(body.to_inputstream)
26
+ end
27
+
28
+ def onResponseHeader(name, value)
29
+ @response[name.to_s] = value.to_s
30
+ end
31
+
32
+ def onResponseComplete
33
+ @client.clear(self)
34
+ @response.status = getResponseStatus
35
+ @response.body = getResponseContent
36
+ run_callback
37
+ end
38
+
39
+ def onExpire
40
+ @client.clear(self)
41
+ @response.timeout = true
42
+ @response.status = -1
43
+ @response.body = nil
44
+ run_callback
45
+ end
46
+
47
+ def onException(error)
48
+ @exception ||= error
49
+ end
50
+
51
+ def onConnectionFailed(error)
52
+ @exception ||= error
53
+ end
54
+
55
+ def run_callback
56
+ begin
57
+ @callback.call(@response)
58
+ rescue => error
59
+ onException(error)
60
+ end
61
+ end
62
+
63
+ def waitForDone
64
+ super
65
+ throw(@exception) if @exception
66
+ end
67
+ #
68
+ # def finished?
69
+ # #FIXME: Implement.
70
+ # end
71
+ end
72
+ end
@@ -0,0 +1,34 @@
1
+ require 'rack/response'
2
+
3
+ module Mizuno
4
+ class ClientResponse
5
+ include Rack::Response::Helpers
6
+
7
+ attr_accessor :url, :status, :headers, :body, :ssl, :timeout
8
+
9
+ def initialize(url)
10
+ @url = url
11
+ @headers = Rack::Utils::HeaderHash.new
12
+ end
13
+
14
+ def [](key)
15
+ @headers[key]
16
+ end
17
+
18
+ def []=(key, value)
19
+ @headers[key] = value
20
+ end
21
+
22
+ def ssl?
23
+ @ssl == true
24
+ end
25
+
26
+ def timeout?
27
+ (@timeout == true) or (@status == 408)
28
+ end
29
+
30
+ def success?
31
+ successful? or redirect?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
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
+ class Body
12
+ include Rack::Utils
13
+
14
+ def initialize(body)
15
+ @body = body
16
+ end
17
+
18
+ def each(&block)
19
+ @body.each(&block)
20
+ end
21
+
22
+ def close
23
+ @body.close if @body.respond_to?(:close)
24
+ end
25
+ end
26
+
27
+ def initialize(app)
28
+ @app = app
29
+ end
30
+
31
+ def call(env)
32
+ @app.call(env)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,259 @@
1
+ require 'stringio'
2
+ require 'rack/response'
3
+
4
+ #
5
+ # Wraps a Rack application in a Jetty handler.
6
+ #
7
+ module Mizuno
8
+ java_import 'org.eclipse.jetty.server.handler.AbstractHandler'
9
+
10
+ class RackHandler < AbstractHandler
11
+ java_import 'java.io.FileInputStream'
12
+ java_import 'org.eclipse.jetty.continuation.ContinuationSupport'
13
+
14
+ # Regex for splitting on newlines.
15
+ NEWLINE = /\n/
16
+
17
+ def initialize(server)
18
+ @server = server
19
+ super()
20
+ end
21
+
22
+ #
23
+ # Sets the Rack application that handles requests sent to this
24
+ # Jetty handler.
25
+ #
26
+ def rackup(app)
27
+ @app = app
28
+ end
29
+
30
+ java_signature %{@Override void handle(String target,
31
+ Request baseRequest, HttpServletRequest request,
32
+ HttpServletResponse response) throws IOException, ServletException}
33
+
34
+ #
35
+ # Takes an incoming request (as a HttpServletRequest) and dispatches
36
+ # it to the rack application setup via [rackup]. All this really
37
+ # involves is translating the various bits of the Servlet API into
38
+ # the Rack API on the way in, and translating the response back on
39
+ # the way out.
40
+ #
41
+ # Also, we implement a common extension to the Rack api for
42
+ # asynchronous request processing. We supply an 'async.callback'
43
+ # parameter in env to the Rack application. If we catch an
44
+ # :async symbol thrown by the app, we initiate a Jetty continuation.
45
+ #
46
+ # When 'async.callback' gets a response with empty headers and an
47
+ # empty body, we declare the async response finished.
48
+ #
49
+ def handle(target, base_request, request, response)
50
+ handle_exceptions(response) do
51
+ # Turn the ServletRequest into a Rack env hash
52
+ env = servlet_to_rack(request)
53
+
54
+ # Handle asynchronous responses via Servlet continuations.
55
+ continuation = ContinuationSupport.getContinuation(request)
56
+
57
+ # If this is an expired connection, do nothing.
58
+ return if continuation.isExpired
59
+
60
+ # We should never be re-dispatched.
61
+ raise("Request re-dispatched.") unless continuation.isInitial
62
+
63
+ # Add our own special bits to the rack environment so that
64
+ # Rack middleware can have access to the Java internals.
65
+ env['rack.java.servlet'] = true
66
+ env['rack.java.servlet.request'] = request
67
+ env['rack.java.servlet.response'] = response
68
+ env['rack.java.servlet.continuation'] = continuation
69
+
70
+ # Add an callback that can be used to add results to the
71
+ # response asynchronously.
72
+ env['async.callback'] = Proc.new do |rack_response|
73
+ servlet_response = continuation.getServletResponse
74
+ rack_to_servlet(rack_response, servlet_response) \
75
+ and continuation.complete
76
+ end
77
+
78
+ # Execute the Rack request.
79
+ catch(:async) do
80
+ rack_response = @app.call(env)
81
+
82
+ # For apps that don't throw :async.
83
+ unless(rack_response[0] == -1)
84
+ # Nope, nothing asynchronous here.
85
+ rack_to_servlet(rack_response, response)
86
+ return
87
+ end
88
+ end
89
+
90
+ # If we got here, this is a continuation.
91
+ continuation.suspend(response)
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ #
98
+ # Turns a Servlet request into a Rack request hash.
99
+ #
100
+ def servlet_to_rack(request)
101
+ # The Rack request that we will pass on.
102
+ env = Hash.new
103
+
104
+ # Map Servlet bits to Rack bits.
105
+ env['REQUEST_METHOD'] = request.getMethod
106
+ env['QUERY_STRING'] = request.getQueryString.to_s
107
+ env['SERVER_NAME'] = request.getServerName
108
+ env['SERVER_PORT'] = request.getServerPort.to_s
109
+ env['rack.version'] = Rack::VERSION
110
+ env['rack.url_scheme'] = request.getScheme
111
+ env['HTTP_VERSION'] = request.getProtocol
112
+ env["SERVER_PROTOCOL"] = request.getProtocol
113
+ env['REMOTE_ADDR'] = request.getRemoteAddr
114
+ env['REMOTE_HOST'] = request.getRemoteHost
115
+
116
+ # request.getPathInfo seems to be blank, so we're using the URI.
117
+ env['REQUEST_PATH'] = request.getRequestURI
118
+ env['PATH_INFO'] = request.getRequestURI
119
+ env['SCRIPT_NAME'] = ""
120
+
121
+ # Rack says URI, but it hands off a URL.
122
+ env['REQUEST_URI'] = request.getRequestURL.to_s
123
+
124
+ # Java chops off the query string, but a Rack application will
125
+ # expect it, so we'll add it back if present
126
+ env['REQUEST_URI'] << "?#{env['QUERY_STRING']}" \
127
+ if env['QUERY_STRING']
128
+
129
+ # JRuby is like the matrix, only there's no spoon or fork().
130
+ env['rack.multiprocess'] = false
131
+ env['rack.multithread'] = true
132
+ env['rack.run_once'] = false
133
+
134
+ # The input stream is a wrapper around the Java InputStream.
135
+ env['rack.input'] = @server.rewindable(request)
136
+
137
+ # Force encoding if we're on Ruby 1.9
138
+ env['rack.input'].set_encoding(Encoding.find("ASCII-8BIT")) \
139
+ if env['rack.input'].respond_to?(:set_encoding)
140
+
141
+ # Populate the HTTP headers.
142
+ request.getHeaderNames.each do |header_name|
143
+ header = header_name.to_s.upcase.tr('-', '_')
144
+ env["HTTP_#{header}"] = request.getHeader(header_name)
145
+ end
146
+
147
+ # Rack Weirdness: HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH
148
+ # both need to have the HTTP_ part dropped.
149
+ env["CONTENT_TYPE"] = env.delete("HTTP_CONTENT_TYPE") \
150
+ if env["HTTP_CONTENT_TYPE"]
151
+ env["CONTENT_LENGTH"] = env.delete("HTTP_CONTENT_LENGTH") \
152
+ if env["HTTP_CONTENT_LENGTH"]
153
+
154
+ # Route errors through the logger.
155
+ # env['rack.errors'] ||= @server.logger
156
+ # env['rack.logger'] ||= @server.logger
157
+
158
+ # All done, hand back the Rack request.
159
+ return(env)
160
+ end
161
+
162
+ #
163
+ # Turns a Rack response into a Servlet response; can be called
164
+ # multiple times. Returns true if this is the full request (either
165
+ # a synchronous request or the last part of an async request),
166
+ # false otherwise.
167
+ #
168
+ # Note that keep-alive *only* happens if we get either a pathname
169
+ # (because we can find the length ourselves), or if we get a
170
+ # Content-Length header as part of the response. While we can
171
+ # readily buffer the response object to figure out how long it is,
172
+ # we have no guarantee that we aren't going to be buffering
173
+ # something *huge*.
174
+ #
175
+ # http://docstore.mik.ua/orelly/java-ent/servlet/ch05_03.htm
176
+ #
177
+ def rack_to_servlet(rack_response, response)
178
+ # Split apart the Rack response.
179
+ status, headers, body = rack_response
180
+
181
+ # We assume the request is finished if we got empty headers,
182
+ # an empty body, and we have a committed response.
183
+ finished = (headers.empty? and \
184
+ body.respond_to?(:empty?) and body.empty?)
185
+ return(true) if (finished and response.isCommitted)
186
+
187
+ # No need to send headers again if we've already shipped
188
+ # data out on an async request.
189
+ unless(response.isCommitted)
190
+ # Set the HTTP status code.
191
+ response.setStatus(status.to_i)
192
+
193
+ # Servlets require that we set Content-Length and
194
+ # Content-Type differently than other headers.
195
+ content_length = headers.delete('Content-Length') \
196
+ and response.setContentLength(content_length.to_i)
197
+ content_type = headers.delete('Content-Type') \
198
+ and response.setContentType(content_type)
199
+
200
+ # Add all the result headers.
201
+ headers.each { |h, v| v.split(NEWLINE).each { |l|
202
+ response.addHeader(h, l) } }
203
+ end
204
+
205
+ # How else would we write output?
206
+ output = response.getOutputStream
207
+
208
+ # Turn the body into something nice and Java-y.
209
+ if(body.respond_to?(:to_path))
210
+ # We've been told to serve a file; use FileInputStream to
211
+ # stream the file directly to the servlet, because this
212
+ # is a lot faster than doing it with Ruby.
213
+ file = java.io.File.new(body.to_path)
214
+
215
+ # We set the content-length so we can use Keep-Alive,
216
+ # unless this is an async request.
217
+ response.setContentLength(file.length) \
218
+ unless content_length
219
+
220
+ # Stream the file directly.
221
+ buffer = Java::byte[4096].new
222
+ input_stream = FileInputStream.new(file)
223
+ while((count = input_stream.read(buffer)) != -1)
224
+ output.write(buffer, 0, count)
225
+ end
226
+ input_stream.close
227
+ else
228
+ body.each do |chunk|
229
+ output.write(chunk.to_java_bytes)
230
+ output.flush
231
+ end
232
+ end
233
+
234
+ # Close the body if we're supposed to.
235
+ body.close if body.respond_to?(:close)
236
+
237
+ # All done.
238
+ output.flush
239
+ end
240
+
241
+ #
242
+ # Handle exceptions, returning a generic 500 error response.
243
+ #
244
+ def handle_exceptions(response)
245
+ begin
246
+ yield
247
+ rescue => error
248
+ message = "Exception: #{error}"
249
+ message << "\n#{error.backtrace.join("\n")}" \
250
+ if (error.respond_to?(:backtrace))
251
+ #Server.logger.error(message)
252
+ $stderr.puts(message)
253
+ return if response.isCommitted
254
+ response.reset
255
+ response.setStatus(500)
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,152 @@
1
+ require 'pathname'
2
+ require 'thread'
3
+
4
+ #:nodoc:
5
+ module Mizuno
6
+ #
7
+ # Middleware for reloading production applications; works exactly
8
+ # like Rack::Reloader, but rather than checking for any changed
9
+ # file, only looks at one specific file.
10
+ #
11
+ # Also allows for explicit reloading via a class method, as well as
12
+ # by sending a SIGHUP to the process.
13
+ #
14
+ class Reloader
15
+ @reloaders = []
16
+
17
+ @trigger = 'tmp/restart.txt'
18
+
19
+ class << self
20
+ attr_accessor :logger, :trigger, :reloaders
21
+ end
22
+
23
+ def Reloader.reload!
24
+ reloaders.each { |r| r.reload!(true) }
25
+ end
26
+
27
+ def Reloader.add(reloader)
28
+ Thread.exclusive do
29
+ @logger ||= Mizuno::Server.logger
30
+ @reloaders << reloader
31
+ end
32
+ end
33
+
34
+ def initialize(app, interval = 1)
35
+ Reloader.add(self)
36
+ @app = app
37
+ @interval = interval
38
+ @trigger = self.class.trigger
39
+ @logger = self.class.logger
40
+ @updated = @threshold = Time.now.to_i
41
+ end
42
+
43
+ #
44
+ # Reload @app on request.
45
+ #
46
+ def call(env)
47
+ Thread.exclusive { reload! }
48
+ @app.call(env)
49
+ end
50
+
51
+ #
52
+ # Reloads the application if (a) we haven't reloaded in
53
+ # @interval seconds, (b) the trigger file has been touched
54
+ # since our last check, and (c) some other thread hasn't handled
55
+ # the update.
56
+ #
57
+ def reload!(force = false)
58
+ return unless (Time.now.to_i > @threshold)
59
+ @threshold = Time.now.to_i + @interval
60
+ return unless (force or \
61
+ ((timestamp = mtime(@trigger)).to_i > @updated))
62
+ timestamp ||= Time.now.to_i
63
+
64
+ # Check updated files to ensure they're loadable.
65
+ missing, errors = 0, 0
66
+ files = find_files_for_reload do |file, file_mtime|
67
+ next(missing += 1 and nil) unless file_mtime
68
+ next unless (file_mtime >= @updated)
69
+ next(errors += 1 and nil) unless verify(file)
70
+ file
71
+ end
72
+
73
+ # Cowardly fail if we can't load something.
74
+ @logger.debug("#{missing} files missing during reload.") \
75
+ if (missing > 0)
76
+ return(@logger.error("#{errors} errors, not reloading.")) \
77
+ if (errors > 0)
78
+
79
+ # Reload everything that's changed.
80
+ files.each do |file|
81
+ next unless file
82
+ @logger.info("Reloading #{file}")
83
+ load(file)
84
+ end
85
+ @updated = timestamp
86
+ end
87
+
88
+ #
89
+ # Walk through the list of every file we've loaded.
90
+ #
91
+ def find_files_for_reload
92
+ paths = [ './', *$LOAD_PATH ].uniq
93
+ [ $0, *$LOADED_FEATURES ].uniq.map do |file|
94
+ next if file =~ /\.(so|bundle)$/
95
+ yield(find(file, paths))
96
+ end
97
+ end
98
+
99
+ #
100
+ # Returns true if the file is loadable; uses the wrapper
101
+ # functionality of Kernel#load to protect the global namespace.
102
+ #
103
+ def verify(file)
104
+ begin
105
+ @logger.debug("Verifying #{file}")
106
+ load(file, true)
107
+ return(true)
108
+ rescue => error
109
+ @logger.error("Failed to verify #{file}: #{error.to_s}")
110
+ error.backtrace.each { |l| @logger.error(" #{l}") }
111
+ end
112
+ end
113
+
114
+ #
115
+ # Takes a relative or absolute +file+ name, a couple possible
116
+ # +paths+ that the +file+ might reside in. Returns a tuple of
117
+ # the full path where the file was found and its modification
118
+ # time, or nil if not found.
119
+ #
120
+ def find(file, paths)
121
+ if(Pathname.new(file).absolute?)
122
+ return unless (timestamp = mtime(file))
123
+ @logger.debug("Found #{file}")
124
+ [ file, timestamp ]
125
+ else
126
+ paths.each do |path|
127
+ fullpath = File.expand_path((File.join(path, file)))
128
+ next unless (timestamp = mtime(fullpath))
129
+ @logger.debug("Found #{file} in #{fullpath}")
130
+ return([ fullpath, timestamp ])
131
+ end
132
+ return(nil)
133
+ end
134
+ end
135
+
136
+ #
137
+ # Returns the modification time of _file_.
138
+ #
139
+ def mtime(file)
140
+ begin
141
+ return unless file
142
+ stat = File.stat(file)
143
+ stat.file? ? stat.mtime.to_i : nil
144
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH
145
+ nil
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # Reload on SIGHUP.
152
+ trap("SIGHUP") { Mizuno::Reloader.reload! }