rubylet 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Common Ground Publishing
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/rubylet.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'rubylet/servlet'
2
+ require 'rubylet/environment'
@@ -0,0 +1,138 @@
1
+ require 'monitor'
2
+
3
+ require 'rubylet/errors'
4
+ require 'rubylet/logger'
5
+ require 'rubylet/session'
6
+
7
+ class Rubylet::Environment < Hash
8
+ include MonitorMixin
9
+
10
+ def initialize(req, servlet)
11
+ super() # required to initialize MonitorMixin
12
+
13
+ if req.respond_to?(:isAsyncSupported) && req.isAsyncSupported
14
+ @async_callback_condition = new_cond
15
+ @async_callback = nil
16
+ self['async.callback'] = method(:forward_to_async_callback)
17
+ end
18
+
19
+ load_headers(req)
20
+ load(req, servlet)
21
+ end
22
+
23
+ def normalize(raw)
24
+ if raw =~ /^Content-(Type|Length)$/i
25
+ raw
26
+ else
27
+ 'HTTP_' + raw
28
+ end.upcase.gsub(/-/, '_')
29
+ end
30
+ private :normalize
31
+
32
+ # Copied from JRuby-Rack rack/handler/servlet.rb
33
+ def load_headers(req)
34
+ req.getHeaderNames.each do |h|
35
+ self[normalize(h)] = req.getHeader(h)
36
+ end
37
+ end
38
+ private :load_headers
39
+
40
+ # Strip a lone slash and also reduce duplicate '/' to a single
41
+ # slash.
42
+ def clean_slashes(str)
43
+ no_dups = str.gsub(%r{/+}, '/')
44
+ no_dups == '/' ? '' : no_dups
45
+ end
46
+ private :clean_slashes
47
+
48
+ # Register an async callback. This block will be called
49
+ # when a Rack application calls the 'async.callback' proc.
50
+ #
51
+ # Calls to 'async.callback' will block until another
52
+ # thread registers a handler by calling this method.
53
+ def on_async_callback(&block)
54
+ synchronize do
55
+ @async_callback = block
56
+ @async_callback_condition.broadcast
57
+ end
58
+ end
59
+
60
+ def forward_to_async_callback(resp)
61
+ synchronize do
62
+ # wait until someone sets @async_callback
63
+ @async_callback_condition.wait_until { @async_callback }
64
+ # pass resp on to the callback
65
+ @async_callback.call(resp)
66
+ end
67
+ end
68
+ private :forward_to_async_callback
69
+
70
+ # Load the Rack environment from a servlet request and context.
71
+ #
72
+ # @param [javax.servlet.http.HttpServletRequest] req
73
+ # @param [javax.servlet.ServletContext context
74
+ def load(req, servlet)
75
+ context = servlet.context
76
+
77
+ self['REQUEST_METHOD'] = req.getMethod
78
+
79
+ # context path joined with servlet_path, but not nil and empty
80
+ # string rather than '/'
81
+ self['SCRIPT_NAME'] =
82
+ begin
83
+ context_path = req.context_path || '/'
84
+ servlet_path = req.servlet_path || ''
85
+ clean_slashes(context_path + servlet_path)
86
+ end
87
+
88
+ self['PATH_INFO'] = clean_slashes(req.path_info || '')
89
+ self['QUERY_STRING'] = req.getQueryString || ''
90
+ self['SERVER_NAME'] = req.getServerName
91
+ self['SERVER_PORT'] = req.getServerPort.to_s
92
+
93
+ self['rack.version'] = ::Rack::VERSION
94
+ self['rack.url_scheme'] = req.getScheme
95
+
96
+ # TODO: this is not rewindable in violation of the Rack
97
+ # spec. Requiring rewind ability on every request seems
98
+ # a bit much, particularly since it would be trivial to
99
+ # wrap the io in a helper buffer as-needed, but should
100
+ # probably implement this eventually.
101
+ #
102
+ # @see http://rack.rubyforge.org/doc/SPEC.html
103
+ self['rack.input'] = req.getInputStream.to_io
104
+
105
+ self['rack.errors'] = Rubylet::Errors.new(context)
106
+ self['rack.multithread'] = true
107
+ self['rack.multiprocess'] = false
108
+ self['rack.run_once'] = false
109
+
110
+ if servlet.param('useSessions')
111
+ self['rack.session'] = Rubylet::Session.new(req)
112
+ end
113
+
114
+ if servlet.param('userLogger')
115
+ self['rack.logger'] = Rubylet::Logger.new(context)
116
+ end
117
+
118
+ self['REMOTE_ADDR'] = req.getRemoteAddr
119
+ self['REMOTE_HOST'] = req.getRemoteHost
120
+ self['REMOTE_USER'] = req.getRemoteUser
121
+ self['REMOTE_PORT'] = req.getRemotePort.to_s
122
+ self['REQUEST_PATH'] = req.getPathInfo
123
+ # note, ruby side is URI, java side is URL
124
+ self['REQUEST_URI'] =
125
+ begin
126
+ q = req.getQueryString
127
+ req.getRequestURL.to_s + (q ? ('?' + q) : '')
128
+ end
129
+
130
+ self['SERVER_PROTOCOL'] = req.getProtocol
131
+ self['SERVER_SOFTWARE'] = context.getServerInfo
132
+
133
+ self['java.context_path'] = req.getContextPath
134
+ self['java.path_info'] = req.getPathInfo
135
+ self['java.servlet_path'] = req.getServletPath
136
+ end
137
+ private :load
138
+ end
@@ -0,0 +1,23 @@
1
+ # Implements the rack.errors interface, logging everything via
2
+ # ServletContext#log
3
+ module Rubylet
4
+ class Errors
5
+ def initialize(context)
6
+ @context = context
7
+ end
8
+
9
+ def puts(obj)
10
+ write(obj.to_s)
11
+ end
12
+
13
+ def write(str)
14
+ @context.log(str)
15
+ end
16
+
17
+ # A no-op for ServletContext#log
18
+ def flush; end
19
+
20
+ # Spec says this must never be called
21
+ def close; end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ require 'java'
2
+ require 'forwardable'
3
+
4
+ # Wraps servlet InputStream with a buffer to support Rack required #rewind.
5
+ class Rubylet::Input
6
+ extend Forwardable
7
+
8
+ MAX_BUFFER = 500 * 1024 # 500 kiB
9
+
10
+ def_delegators :@io, :gets, :read, :each, :close
11
+
12
+ def initialize(req)
13
+ @stream = java.io.BufferedInputStream.new(req.getInputStream,
14
+ req.getContentLength)
15
+ if req.getContentLength > 0 && req.getContentLength < MAX_BUFFER
16
+ @stream.mark(req.getContentLength)
17
+ else
18
+ # TODO: buffer to a file or something; use jruby-rack's RewindableInputStream
19
+ req.getServletContext.log(
20
+ "WARN: input stream of size #{req.getContentLength}; " +
21
+ "rack #rewind only supported to first #{MAX_BUFFER/1024} kiB")
22
+ @stream.mark(MAX_BUFFER)
23
+ end
24
+
25
+ @io = stream.to_io
26
+ end
27
+
28
+ def rewind
29
+ @stream.reset
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubylet/errors'
2
+
3
+ # TODO: should this wrap slf4j or something? Or is it typically overridden
4
+ # by the rack application?
5
+ module Rubylet
6
+ class Logger < Rubylet::Errors
7
+
8
+ def info(message = nil, &block)
9
+ log(:info, message, &block)
10
+ end
11
+
12
+ def debug(message = nil, &block)
13
+ log(:debug, message, &block)
14
+ end
15
+
16
+ def warn(message = nil, &block)
17
+ log(:warn, message, &block)
18
+ end
19
+
20
+ def error(message = nil, &block)
21
+ log(:error, message, &block)
22
+ end
23
+
24
+ def fatal(message = nil, &block)
25
+ log(:fatal, message, &block)
26
+ end
27
+
28
+ def log(level, message = nil, &block)
29
+ msg = if message
30
+ message
31
+ elsif block_given?
32
+ yield
33
+ end
34
+
35
+ write("#{level.to_s.upcase}: #{msg}")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,80 @@
1
+ require 'tempfile'
2
+
3
+ # A memory/file IO buffer. Data will be written to a memory buffer up
4
+ # to a limit, after which a file buffer is used. Supports Rack input
5
+ # stream operations. Not threadsafe.
6
+ module Rubylet
7
+ class MemFileIO
8
+ DEFAULT_LIMIT = 4096 * 4
9
+
10
+ # @param [Integer] limit the max size of the first (memory) buffer
11
+ def initialize(limit = DEFAULT_LIMIT)
12
+ @limit = limit
13
+ @mem = StringIO.new
14
+ @file = nil
15
+ end
16
+
17
+ def puts(str)
18
+ for_write(str.size + $/.size).puts(str)
19
+ end
20
+
21
+ def write(str)
22
+ for_write(str.size).write(str)
23
+ end
24
+
25
+ def gets
26
+ for_read.gets
27
+ end
28
+
29
+ def read(length = nil, buffer = nil)
30
+ if buffer
31
+ for_read.read(length, buffer)
32
+ else
33
+ for_read.read(length)
34
+ end
35
+ end
36
+
37
+ def each(&block)
38
+ for_read.each(&block)
39
+ end
40
+
41
+ def rewind
42
+ for_read.rewind
43
+ end
44
+
45
+ def close
46
+ for_read.close
47
+ end
48
+
49
+ private
50
+
51
+ def for_read
52
+ @mem || @file
53
+ end
54
+
55
+ def switch_to_file
56
+ mem = @mem
57
+ @mem = nil
58
+ mem.rewind
59
+ @file = make_file(mem.read)
60
+ end
61
+
62
+ def for_write(size)
63
+ if @file
64
+ @file
65
+ elsif (@mem.size + size) <= @limit
66
+ @mem
67
+ else
68
+ switch_to_file
69
+ @file
70
+ end
71
+ end
72
+
73
+ def make_file(data)
74
+ file = Tempfile.new('MemFileBacking')
75
+ file.unlink
76
+ file.write(data)
77
+ file
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,66 @@
1
+ require 'rubylet/tee_io'
2
+ require 'rubylet/mem_file_io'
3
+ require 'stringio'
4
+
5
+ # Wraps an IO with a memory and file backed buffer to support Rack's
6
+ # required #rewind.
7
+ #
8
+ # Only some IO methods are supported.
9
+ module Rubylet
10
+ class RewindableIO
11
+ def initialize(io, limit = MemFileIO::DEFAULT_LIMIT)
12
+ @buf = MemFileIO.new(limit)
13
+ @io = TeeIO.new(io, @buf)
14
+ end
15
+
16
+ def gets
17
+ str = @buf.gets
18
+ if str.nil? # end of buffer
19
+ @io.gets
20
+ elsif str.end_with?($/) # good, we got a whole line
21
+ str
22
+ else # partial line
23
+ str + (@io.gets || '')
24
+ end
25
+ end
26
+
27
+ def read(length = nil, buffer = nil)
28
+ if length.nil? && buffer.nil?
29
+ @buf.read + @io.read
30
+ elsif length.nil?
31
+ @buf.read(nil, buffer)
32
+ @io.read(nil, buffer)
33
+ elsif buffer.nil?
34
+ str = ''
35
+ @buf.read(length, str)
36
+ if (str.size == length)
37
+ @io.read(length - str.size, str)
38
+ end
39
+ str
40
+ else # both non-nil
41
+ size0 = buffer.size
42
+ @buf.read(length, buffer)
43
+ from_buf = buffer.size - size0
44
+ if (from_buf < length)
45
+ @io.read(length - from_buf, buffer)
46
+ end
47
+ buffer
48
+ end
49
+ end
50
+
51
+ def each
52
+ while s = gets
53
+ yield s
54
+ end
55
+ end
56
+
57
+ def rewind
58
+ @buf.rewind
59
+ end
60
+
61
+ def close
62
+ @buf.close
63
+ @io.close
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,149 @@
1
+ require 'java'
2
+ require 'rack'
3
+
4
+ module Rubylet
5
+ # Implements the Servlet API in Ruby as a Rack server.
6
+ class Servlet
7
+ include Java::JavaxServlet::Servlet
8
+
9
+ attr_reader :servlet_config, :app, :context
10
+
11
+ # @param [javax.servlet.ServletConfig] servletConfig
12
+ def init(servletConfig)
13
+ @servlet_config = servletConfig
14
+ @context = servletConfig.getServletContext
15
+
16
+ rackup_file = param('rackupFile') || 'config.ru'
17
+ @app, _opts = Rack::Builder.parse_file(rackup_file)
18
+
19
+ if defined?(Rails) && defined?(Rails::Application) && (@app < Rails::Application)
20
+ servlet_path = @servlet_config.getInitParameter('servletPath')
21
+ relative_root = if servlet_path
22
+ File.join(@context.context_path, servlet_path)
23
+ else
24
+ @context.context_path
25
+ end
26
+ app.config.action_controller.relative_url_root = relative_root
27
+ ActionController::Base.config.relative_url_root = relative_root
28
+ end
29
+ end
30
+
31
+ # def param(config, name)
32
+ # config.getInitParameter(name) ||
33
+ # config.getServletContext.getInitParameter(name)
34
+ # end
35
+
36
+ def param(name)
37
+ @servlet_config.getInitParameter(name) ||
38
+ @servlet_config.getServletContext.getInitParameter(name)
39
+ end
40
+
41
+ def destroy
42
+ # no-op
43
+ end
44
+
45
+ def to_s
46
+ "#<#{self.class} @app=#{@app}>"
47
+ end
48
+
49
+ alias :servlet_info :to_s
50
+
51
+ # @param [javax.servlet.ServletRequest] req a Java servlet request,
52
+ # assumed to be an HttpServletRequest
53
+ #
54
+ # @param [javax.servlet.ServletResponse] resp a Java servlet response
55
+ # assumed to be an HttpServletResponse
56
+ def service(req, resp)
57
+ env = Environment.new(req, self)
58
+
59
+ catch(:async) do
60
+ status, headers, body = app.call(env) # may throw async
61
+ throw :async if status == -1 # alternate means of indicating async
62
+ respond(resp, env, status, headers, body)
63
+ return # :async not thrown
64
+ end
65
+
66
+ # :async was thrown; this only works since Servlet 3.0
67
+ # web.xml must also have <async-supported>true<async-supported> in
68
+ # all servlets and filters in the chain.
69
+ #
70
+ # TODO: this works in proof-of-concept! anything more todo?
71
+ #
72
+ # TODO: Rack async doesn't seem to be standardized yet... In particular,
73
+ # Thin provides an 'async.close' that (I think) can be used to
74
+ # close the response connection after streaming in data asynchronously.
75
+ # For now, this code only allows a one-time deferal of the body (i.e.
76
+ # when 'async.callback' is called, the body is sent out and the
77
+ # connection closed immediately.)
78
+ #
79
+ # TODO: because of the above, there isn't a way to quickly send headers
80
+ # but then delay the body.
81
+ #
82
+ # Example Rack application:
83
+ #
84
+ # require 'thread'
85
+ #
86
+ # class AsyncExample
87
+ # def call(env)
88
+ # cb = env['async.callback']
89
+ #
90
+ # Thread.new do
91
+ # sleep 5 # long task, wait for message, etc.
92
+ # body = ['Hello, World!']
93
+ # cb.call [200, {'Content-Type' => 'text/plain'}, body]
94
+ # end
95
+ #
96
+ # throw :async
97
+ # end
98
+ # end
99
+ async_context = req.startAsync
100
+ env.on_async_callback do |(status, headers, body)|
101
+ resp = async_context.getResponse
102
+ respond(resp, env, status, headers, body)
103
+ async_context.complete
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def respond(resp, env, status, headers, body)
110
+ resp.setStatus(status)
111
+ headers.each do |k, v|
112
+ resp.setHeader k, v
113
+ end
114
+ # commit the response and send the headers to the client
115
+ resp.flushBuffer
116
+
117
+ if body.respond_to? :to_path
118
+ #env['rack.logger'].warn {
119
+ # "serving static file with ruby: #{body.to_path}"
120
+ #}
121
+
122
+ # TODO: faster to user pure java implementation? Probably better to
123
+ # either not have ruby serve static files or to put some cache out
124
+ # front.
125
+ write_body(body, resp.getOutputStream) { |part| part.to_java_bytes }
126
+ else
127
+ write_body(body, resp.getWriter)
128
+ end
129
+ end
130
+
131
+ # Write each part of body with writer. Optionally transform each
132
+ # part with the given block. Flush the writer after each
133
+ # part. Ensure body is closed if it responds to :close. Close the
134
+ # writer.
135
+ def write_body(body, writer)
136
+ begin
137
+ body.each do |part|
138
+ writer.write(block_given? ? yield(part) : part)
139
+ writer.flush
140
+ end
141
+ ensure
142
+ body.close if body.respond_to?(:close) rescue nil
143
+ writer.close
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ require 'rubylet/environment'
@@ -0,0 +1,33 @@
1
+ class Rubylet::Session
2
+
3
+ def initialize(req)
4
+ @javaSession = req.getSession
5
+ end
6
+
7
+ def store(key, value)
8
+ @javaSession.setAttribute(key, value)
9
+ end
10
+
11
+ alias_method :[]=, :store
12
+
13
+ def fetch(key, default = nil)
14
+ @javaSession.getAttribute(key) || default
15
+ end
16
+
17
+ alias_method :[], :fetch
18
+
19
+ def delete(key)
20
+ @javaSession.removeAttribute(key)
21
+ end
22
+
23
+ def clear
24
+ @javaSession.getAttributeNames.each {|key| delete(key) }
25
+ end
26
+
27
+ def to_hash
28
+ hash = {}
29
+ @javaSession.getAttributeNames.each {|key| hash[key] = @javaSession.getAttribute(key) }
30
+ hash
31
+ end
32
+
33
+ end
@@ -0,0 +1,43 @@
1
+ # All reads on +io+ are copied to +out+. Only supports IO methods
2
+ # specified by Rack Input Stream spec.
3
+ module Rubylet
4
+ class TeeIO
5
+ def initialize(io, out)
6
+ @io = io
7
+ @out = out
8
+ end
9
+
10
+ # Does not support custom separator per Rack spec
11
+ def gets
12
+ str = @io.gets
13
+ @out.write(str) if str
14
+ str
15
+ end
16
+
17
+ def read(length = nil, buffer = nil)
18
+ str = ''
19
+ ret = @io.read(length, str)
20
+ @out.write(str)
21
+ if buffer
22
+ buffer << str
23
+ if ret.object_id == str.object_id
24
+ buffer
25
+ else
26
+ ret
27
+ end
28
+ else
29
+ ret
30
+ end
31
+ end
32
+
33
+ def each
34
+ while s = gets
35
+ yield s
36
+ end
37
+ end
38
+
39
+ def close
40
+ @io.close
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require 'rubylet/mem_file_io'
3
+
4
+ module Rubylet
5
+
6
+ describe MemFileIO do
7
+
8
+ it 'writes data with puts' do
9
+ @io = MemFileIO.new
10
+ @io.puts('hello')
11
+ @io.rewind
12
+ @io.read.must_equal "hello#{$/}"
13
+ end
14
+
15
+ it 'writes data with write' do
16
+ @io = MemFileIO.new
17
+ @io.write('hello')
18
+ @io.rewind
19
+ @io.read.must_equal 'hello'
20
+ end
21
+
22
+ it 'allows multiple rewind' do
23
+ @io = MemFileIO.new
24
+
25
+ @io.write('hello')
26
+ @io.rewind
27
+ @io.read.must_equal 'hello'
28
+
29
+ @io.write('hello')
30
+ @io.rewind
31
+ @io.read.must_equal 'hellohello'
32
+ end
33
+
34
+ it 'overwrites? (FIXME: is this correct?)' do
35
+ @io = MemFileIO.new
36
+
37
+ @io.write('hello')
38
+ @io.rewind
39
+ @io.read.must_equal 'hello'
40
+
41
+ @io.rewind
42
+ @io.write('***')
43
+ @io.rewind
44
+ @io.read.must_equal '***lo'
45
+ end
46
+
47
+ it 'switches to backing file' do
48
+ @io = MemFileIO.new(4)
49
+ @io.write('1234')
50
+ @io.write('5678')
51
+ @io.rewind
52
+ @io.read.must_equal '12345678'
53
+ end
54
+
55
+ it 'returns nil on EOF' do
56
+ @io = MemFileIO.new
57
+ @io.read
58
+ @io.read(1).must_equal nil
59
+ end
60
+
61
+ it 'returns empty string on EOF' do
62
+ @io = MemFileIO.new
63
+ @io.read
64
+ @io.read.must_equal ''
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'rubylet/rewindable_io'
3
+ require 'stringio'
4
+
5
+ module Rubylet
6
+
7
+ describe RewindableIO do
8
+
9
+ data = "some data\nwith newlines\nanother line"
10
+
11
+ before :each do
12
+ @io = StringIO.new
13
+ @io.write(data)
14
+ @io.rewind
15
+
16
+ @reio = RewindableIO.new(@io)
17
+ end
18
+
19
+ it 'rewinds' do
20
+ @reio.read.must_equal data
21
+ @reio.rewind
22
+ @reio.read.must_equal data
23
+ end
24
+
25
+ it 'rewinds with file spill over' do
26
+ expected = ''
27
+ 10.times do
28
+ @io.write(data)
29
+ expected << data
30
+ end
31
+ @io.rewind
32
+
33
+ @reio = RewindableIO.new(@io, 32)
34
+ @reio.read.must_equal expected
35
+ @reio.rewind
36
+ @reio.read.must_equal expected
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ require 'rubylet/tee_io'
3
+ require 'stringio'
4
+
5
+ module Rubylet
6
+
7
+ describe TeeIO do
8
+
9
+ DATA = "some data\nwith newlines\nanother line"
10
+
11
+ before :each do
12
+ @io = StringIO.new
13
+ @io.write(DATA)
14
+ @io.rewind
15
+
16
+ @out = StringIO.new
17
+
18
+ @tee = TeeIO.new(@io, @out)
19
+ end
20
+
21
+ it 'copies data on read' do
22
+ @tee.read
23
+ @out.rewind
24
+ @out.read.must_equal DATA
25
+ end
26
+
27
+ describe '#gets' do
28
+ it 'copies data on gets' do
29
+ @tee.gets
30
+ @out.rewind
31
+ @out.read.must_equal "some data\n"
32
+ end
33
+
34
+ it 'returns nil on EOF' do
35
+ @tee.read
36
+ @tee.gets.must_equal nil
37
+ end
38
+ end
39
+
40
+ it 'copies data on each' do
41
+ @tee.each {}
42
+ @out.rewind
43
+ @out.read.must_equal DATA
44
+ end
45
+
46
+ it 'copies data byte by byte' do
47
+ @tee.read(1)
48
+ @tee.read(1)
49
+ @out.rewind
50
+ @out.read.must_equal 'so'
51
+ end
52
+
53
+ it 'reads data into a given buffer' do
54
+ buf = String.new
55
+ ret = @tee.read nil, buf
56
+
57
+ ret.object_id.must_equal buf.object_id
58
+
59
+ buf.must_equal DATA
60
+ @out.rewind
61
+ @out.read.must_equal DATA
62
+ end
63
+
64
+ it 'returns nil on EOF' do
65
+ @tee.read
66
+ @tee.read(1).must_equal nil
67
+ end
68
+
69
+ it 'returns empty string on EOF' do
70
+ @tee.read
71
+ @tee.read.must_equal ''
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,3 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'minitest/matchers'
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubylet
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: java
7
+ authors:
8
+ - CG Labs
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: minitest-matchers
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: !binary |-
21
+ MA==
22
+ none: false
23
+ requirement: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: !binary |-
28
+ MA==
29
+ none: false
30
+ prerelease: false
31
+ type: :development
32
+ - !ruby/object:Gem::Dependency
33
+ name: rake
34
+ version_requirements: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: !binary |-
39
+ MA==
40
+ none: false
41
+ requirement: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: !binary |-
46
+ MA==
47
+ none: false
48
+ prerelease: false
49
+ type: :development
50
+ - !ruby/object:Gem::Dependency
51
+ name: version
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: !binary |-
57
+ MQ==
58
+ none: false
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: !binary |-
64
+ MQ==
65
+ none: false
66
+ prerelease: false
67
+ type: :development
68
+ description: Java Servlet implementation that forwards to Rack application
69
+ email:
70
+ - eng@commongroundpublishing.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - VERSION
76
+ - MIT-LICENSE
77
+ - lib/rubylet.rb
78
+ - lib/rubylet/mem_file_io.rb
79
+ - lib/rubylet/environment.rb
80
+ - lib/rubylet/rewindable_io.rb
81
+ - lib/rubylet/input.rb
82
+ - lib/rubylet/tee_io.rb
83
+ - lib/rubylet/errors.rb
84
+ - lib/rubylet/session.rb
85
+ - lib/rubylet/logger.rb
86
+ - lib/rubylet/servlet.rb
87
+ - spec/spec_helper.rb
88
+ - spec/rubylet/rewindable_io_spec.rb
89
+ - spec/rubylet/mem_file_io_spec.rb
90
+ - spec/rubylet/tee_io_spec.rb
91
+ homepage:
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: !binary |-
102
+ MA==
103
+ segments:
104
+ - 0
105
+ hash: 2
106
+ none: false
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: !binary |-
112
+ MA==
113
+ segments:
114
+ - 0
115
+ hash: 2
116
+ none: false
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 1.8.24
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Java Servlet implementation that forwards to Rack application
123
+ test_files: []