rubylet 0.1.0-java

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/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: []