thick 0.0.4-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/CHANGELOG.md +17 -0
- data/LICENSE +22 -0
- data/README.md +71 -0
- data/ROADMAP.md +12 -0
- data/bin/thick +46 -0
- data/lib/jars/netty-3.4.0.Alpha2.jar +0 -0
- data/lib/thick/async_response.rb +53 -0
- data/lib/thick/buffer.rb +42 -0
- data/lib/thick/java.rb +41 -0
- data/lib/thick/pipeline_factory.rb +70 -0
- data/lib/thick/server.rb +31 -0
- data/lib/thick/server_handler.rb +138 -0
- data/lib/thick/version.rb +3 -0
- data/lib/thick.rb +13 -0
- metadata +78 -0
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## 0.4 (xy.xz.2012)
|
|
2
|
+
|
|
3
|
+
* make gem Java specific
|
|
4
|
+
* copy-less request body container
|
|
5
|
+
* async responses
|
|
6
|
+
|
|
7
|
+
## 0.3 (26.3.2012)
|
|
8
|
+
|
|
9
|
+
Update Netty to 3.4.0.Alpha2
|
|
10
|
+
|
|
11
|
+
## 0.2 (26.3.2012)
|
|
12
|
+
|
|
13
|
+
First public version
|
|
14
|
+
|
|
15
|
+
## 0.1 (26.3.2012)
|
|
16
|
+
|
|
17
|
+
Claim the gem ;)
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Portions copyright (c) 2012 Marek Jelen
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Thick
|
|
2
|
+
|
|
3
|
+
Thick is very lightweight web server for JRuby based on excellent Netty library.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Works on several Sinatra and Rails applications. No real testing, yet.
|
|
8
|
+
|
|
9
|
+
## Speed
|
|
10
|
+
|
|
11
|
+
see PERFORMANCE.md
|
|
12
|
+
|
|
13
|
+
## Interfaces
|
|
14
|
+
|
|
15
|
+
Thick provides interfaces to control it's behaviour.
|
|
16
|
+
|
|
17
|
+
### Asynchronous responses
|
|
18
|
+
|
|
19
|
+
Asynchronous responses provide functionality to stream response to the client by chunks.
|
|
20
|
+
|
|
21
|
+
The most basic API looks
|
|
22
|
+
|
|
23
|
+
env['thick.async'].async!(*params, &block)
|
|
24
|
+
|
|
25
|
+
the application has to respond in a standard manner
|
|
26
|
+
|
|
27
|
+
[status, headers, body]
|
|
28
|
+
|
|
29
|
+
the response will be processed and send to the client, however the connection is not closed, instead the block is
|
|
30
|
+
executed with parameters passed as params. From the block the application can stream data to the client
|
|
31
|
+
|
|
32
|
+
env['thick.async'].call(chunk)
|
|
33
|
+
|
|
34
|
+
after the response is completed the application has to close the connection
|
|
35
|
+
|
|
36
|
+
env['thick.async'].close
|
|
37
|
+
|
|
38
|
+
In more complex scenarios the params and block may be omitted. It's up to the application to wait till the response
|
|
39
|
+
header is sent to the client and stream it's data. To simplify the signalization the application may check if it's safe
|
|
40
|
+
to send it's data by.
|
|
41
|
+
|
|
42
|
+
env['thick.async'].ready?
|
|
43
|
+
|
|
44
|
+
if the method's response is positive it's safe to stream the data.
|
|
45
|
+
|
|
46
|
+
In the most complex cases it might be necessary to completely bypass default HTTP response generated by the server. In
|
|
47
|
+
those cases the application calls
|
|
48
|
+
|
|
49
|
+
env['thick.async'].custom!(*params, &block)
|
|
50
|
+
|
|
51
|
+
the method informs the server to not send any response on the connection and do not close the connection.
|
|
52
|
+
The contract is the same as with async! mechanism. The block with parameters params is triggered by the server when
|
|
53
|
+
it's safe to stream data. If the block is not given it's completely up to the application to stream the data.
|
|
54
|
+
The application may check whether the server considers safe to stream data by the same way as with async! response
|
|
55
|
+
|
|
56
|
+
env['thick.async'].ready?
|
|
57
|
+
|
|
58
|
+
In the case of custom responses it should be safe to stream right away without checking the server's opinion. Also with
|
|
59
|
+
custom responses it's not necessary to return standard rack response.
|
|
60
|
+
|
|
61
|
+
## Starting
|
|
62
|
+
|
|
63
|
+
thick [-o <interface>] [-p <port>] [-E <environment>]
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
gem install thick
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT ... let me know if that's a problem for you.
|
data/ROADMAP.md
ADDED
data/bin/thick
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'optparse'
|
|
3
|
+
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
require 'rack'
|
|
6
|
+
|
|
7
|
+
$: << File.expand_path('../../lib', __FILE__)
|
|
8
|
+
require 'thick'
|
|
9
|
+
|
|
10
|
+
options = {}
|
|
11
|
+
|
|
12
|
+
OptionParser.new do |opts|
|
|
13
|
+
|
|
14
|
+
opts.banner = 'Usage: thick [options]'
|
|
15
|
+
|
|
16
|
+
opts.separator ''
|
|
17
|
+
opts.separator 'Ruby options:'
|
|
18
|
+
|
|
19
|
+
opts.separator ''
|
|
20
|
+
opts.separator 'Rack options:'
|
|
21
|
+
|
|
22
|
+
opts.on('-o', '--host HOST', 'listen on HOST (default: 0.0.0.0)') do |host|
|
|
23
|
+
options[:address] = host
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
opts.on('-p', '--port PORT', 'use PORT (default: 9292)') do |port|
|
|
27
|
+
options[:port] = Integer(port)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
opts.on('-E', '--env ENVIRONMENT', 'use ENVIRONMENT for defaults (default: development)') do |e|
|
|
31
|
+
options[:environment] = e
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end.parse!
|
|
35
|
+
|
|
36
|
+
options[:address] ||= '0.0.0.0'
|
|
37
|
+
options[:port] ||= 9292
|
|
38
|
+
options[:environment] ||= 'development'
|
|
39
|
+
|
|
40
|
+
ENV['RACK_ENV'] ||= ENV['RAILS_ENV'] ||= options[:environment]
|
|
41
|
+
|
|
42
|
+
puts "Loading app #{options.inspect}"
|
|
43
|
+
app = Rack::Builder.parse_file('config.ru')
|
|
44
|
+
|
|
45
|
+
puts "Starting server"
|
|
46
|
+
Thick.start(app[0], options)
|
|
Binary file
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Thick
|
|
2
|
+
|
|
3
|
+
class AsyncResponse
|
|
4
|
+
|
|
5
|
+
def initialize(context)
|
|
6
|
+
@context = context
|
|
7
|
+
@ready = false
|
|
8
|
+
@custom = false
|
|
9
|
+
@async = false
|
|
10
|
+
@block = nil
|
|
11
|
+
@params = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(chunk)
|
|
15
|
+
@context.channel.write(Thick::Java::ChannelBuffers.copiedBuffer(chunk.to_s, Thick::Java::CharsetUtil::UTF_8))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def close
|
|
19
|
+
@context.channel.write(Thick::Java::ChannelBuffers::EMPTY_BUFFER).addListener(Thick::Java::ChannelFutureListener::CLOSE)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ready!
|
|
23
|
+
@ready = true
|
|
24
|
+
@block.call(*@params) if @block
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def ready?
|
|
28
|
+
@ready
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def custom!(*params, &block)
|
|
32
|
+
@custom = true
|
|
33
|
+
@block = block
|
|
34
|
+
@params = params
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def custom?
|
|
38
|
+
@custom
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def async!(*params, &block)
|
|
42
|
+
@async = true
|
|
43
|
+
@block = block
|
|
44
|
+
@params = params
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def async?
|
|
48
|
+
@async
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
data/lib/thick/buffer.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Thick
|
|
2
|
+
|
|
3
|
+
class Buffer
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@buffers = []
|
|
7
|
+
@buffer = nil
|
|
8
|
+
@io = nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def io
|
|
12
|
+
@buffer ||= Thick::Java::ChannelBuffers.wrapped_buffer(*@buffers)
|
|
13
|
+
@io ||= Thick::Java::ChannelBufferInputStream.new(@buffer).to_io.binmode
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(data)
|
|
17
|
+
raise ArgumentError unless data.kind_of?(Thick::Java::ChannelBuffer)
|
|
18
|
+
@buffers << data
|
|
19
|
+
@buffer = nil
|
|
20
|
+
@io = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def gets
|
|
24
|
+
self.io.gets
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read(length = nil, buffer = nil)
|
|
28
|
+
buffer ? self.io.read(length, buffer) : self.io.read(length)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def each(&block)
|
|
32
|
+
self.io.each(&block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rewind
|
|
36
|
+
@buffer.reader_index(0) if @buffer
|
|
37
|
+
@io = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
data/lib/thick/java.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Thick
|
|
2
|
+
module Java
|
|
3
|
+
|
|
4
|
+
java_import 'java.io.RandomAccessFile'
|
|
5
|
+
java_import 'java.net.InetSocketAddress'
|
|
6
|
+
java_import 'java.util.concurrent.Executors'
|
|
7
|
+
java_import 'org.jboss.netty.bootstrap.ServerBootstrap'
|
|
8
|
+
java_import 'org.jboss.netty.buffer.ChannelBuffer'
|
|
9
|
+
java_import 'org.jboss.netty.buffer.ChannelBufferInputStream'
|
|
10
|
+
java_import 'org.jboss.netty.buffer.ChannelBuffers'
|
|
11
|
+
java_import 'org.jboss.netty.handler.codec.http.DefaultHttpResponse'
|
|
12
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpContentDecompressor'
|
|
13
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpHeaders'
|
|
14
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpChunk'
|
|
15
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpRequest'
|
|
16
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpRequestDecoder'
|
|
17
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpResponseEncoder'
|
|
18
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpResponseStatus'
|
|
19
|
+
java_import 'org.jboss.netty.handler.codec.http.HttpVersion'
|
|
20
|
+
java_import 'org.jboss.netty.handler.codec.http.QueryStringDecoder'
|
|
21
|
+
java_import 'org.jboss.netty.handler.codec.spdy.SpdyFrameDecoder'
|
|
22
|
+
java_import 'org.jboss.netty.handler.codec.spdy.SpdyFrameEncoder'
|
|
23
|
+
java_import 'org.jboss.netty.handler.codec.spdy.SpdySessionHandler'
|
|
24
|
+
java_import 'org.jboss.netty.handler.execution.ExecutionHandler'
|
|
25
|
+
java_import 'org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor'
|
|
26
|
+
java_import 'org.jboss.netty.handler.logging.LoggingHandler'
|
|
27
|
+
java_import 'org.jboss.netty.handler.stream.ChunkedStream'
|
|
28
|
+
java_import 'org.jboss.netty.handler.stream.ChunkedWriteHandler'
|
|
29
|
+
java_import 'org.jboss.netty.channel.DefaultFileRegion'
|
|
30
|
+
java_import 'org.jboss.netty.channel.ChannelFutureListener'
|
|
31
|
+
java_import 'org.jboss.netty.channel.ChannelPipelineFactory'
|
|
32
|
+
java_import 'org.jboss.netty.channel.Channels'
|
|
33
|
+
java_import 'org.jboss.netty.channel.SimpleChannelUpstreamHandler'
|
|
34
|
+
java_import 'org.jboss.netty.channel.ChannelUpstreamHandler'
|
|
35
|
+
java_import 'org.jboss.netty.channel.ChannelDownstreamHandler'
|
|
36
|
+
java_import 'org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory'
|
|
37
|
+
java_import 'org.jboss.netty.logging.InternalLogLevel'
|
|
38
|
+
java_import 'org.jboss.netty.util.CharsetUtil'
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Thick
|
|
2
|
+
|
|
3
|
+
class TimeProbe
|
|
4
|
+
|
|
5
|
+
include Thick::Java::ChannelUpstreamHandler
|
|
6
|
+
include Thick::Java::ChannelDownstreamHandler
|
|
7
|
+
|
|
8
|
+
def self.setup
|
|
9
|
+
@total = 0
|
|
10
|
+
@count = 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@time = Time.now.to_f
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
TimeProbe.setup
|
|
18
|
+
|
|
19
|
+
def self.time(time)
|
|
20
|
+
@total += time
|
|
21
|
+
@count += 1
|
|
22
|
+
if @count % 100 == 0
|
|
23
|
+
puts "AVG: #{@total / @count}"
|
|
24
|
+
setup
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def handleUpstream(context, event)
|
|
29
|
+
context.send_upstream(event)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def handleDownstream(context, event)
|
|
33
|
+
if event.respond_to?(:getState)
|
|
34
|
+
stop = Time.now.to_f - @time
|
|
35
|
+
self.class.time(stop)
|
|
36
|
+
end
|
|
37
|
+
context.send_downstream(event)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class PipelineFactory
|
|
43
|
+
|
|
44
|
+
include Thick::Java::ChannelPipelineFactory
|
|
45
|
+
|
|
46
|
+
def initialize(application)
|
|
47
|
+
@application = application
|
|
48
|
+
@executor = Thick::Java::ExecutionHandler.new(Thick::Java::OrderedMemoryAwareThreadPoolExecutor.new(20, 0, 0))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def getPipeline
|
|
52
|
+
pipeline = Thick::Java::Channels.pipeline
|
|
53
|
+
|
|
54
|
+
pipeline.add_last('time_probe', TimeProbe.new)
|
|
55
|
+
|
|
56
|
+
pipeline.add_last('http_decoder', Thick::Java::HttpRequestDecoder.new)
|
|
57
|
+
pipeline.add_last('http_encoder', Thick::Java::HttpResponseEncoder.new)
|
|
58
|
+
|
|
59
|
+
pipeline.add_last('decompressor', Thick::Java::HttpContentDecompressor.new)
|
|
60
|
+
|
|
61
|
+
pipeline.add_last('executor', @executor)
|
|
62
|
+
|
|
63
|
+
pipeline.add_last('handler', ServerHandler.new(@application))
|
|
64
|
+
|
|
65
|
+
pipeline
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
data/lib/thick/server.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Thick
|
|
2
|
+
|
|
3
|
+
def self.start(application, options)
|
|
4
|
+
@server = Server.new(application)
|
|
5
|
+
@server.start(:address => options[:address], :port => options[:port])
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Server
|
|
9
|
+
|
|
10
|
+
def initialize(application)
|
|
11
|
+
@factory = Thick::Java::NioServerSocketChannelFactory.new(Thick::Java::Executors.new_cached_thread_pool, Thick::Java::Executors.new_cached_thread_pool)
|
|
12
|
+
@bootstrap = Thick::Java::ServerBootstrap.new(@factory)
|
|
13
|
+
@bootstrap.pipeline_factory = PipelineFactory.new(application)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start(options = {})
|
|
17
|
+
@channel = @bootstrap.bind(Thick::Java::InetSocketAddress.new(options[:address], options[:port]))
|
|
18
|
+
puts 'Server started'
|
|
19
|
+
loop do
|
|
20
|
+
# ToDo: Is this hack needed?
|
|
21
|
+
sleep(100)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def stop
|
|
26
|
+
@channel.close
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
module Thick
|
|
4
|
+
|
|
5
|
+
class ServerHandler < Thick::Java::SimpleChannelUpstreamHandler
|
|
6
|
+
|
|
7
|
+
def initialize(application)
|
|
8
|
+
super()
|
|
9
|
+
@application = application
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def channelConnected(context, event)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def messageReceived(context, event)
|
|
16
|
+
request = event.message
|
|
17
|
+
case request
|
|
18
|
+
when Thick::Java::HttpRequest
|
|
19
|
+
on_request(context, request)
|
|
20
|
+
when Thick::Java::HttpChunk
|
|
21
|
+
on_chunk(context, request)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def serve_file(context, file, from = 0, to = nil)
|
|
26
|
+
raf = Thick::Java::RandomAccessFile.new(file, "r")
|
|
27
|
+
to ||= File.size(file)
|
|
28
|
+
region = Thick::Java::DefaultFileRegion.new(raf.channel, from, to)
|
|
29
|
+
context.channel.write(region)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def on_request(context, request)
|
|
33
|
+
|
|
34
|
+
# Serve file if exists in public directory
|
|
35
|
+
file = File.join('public', request.uri)
|
|
36
|
+
return serve_file(context, file).addListener(Thick::Java::ChannelFutureListener::CLOSE) if !request.uri.index('..') && File.exists?(file) && !File.directory?(file)
|
|
37
|
+
|
|
38
|
+
query_string = request.uri.split('?', 2)
|
|
39
|
+
@env = {
|
|
40
|
+
# Rack specific
|
|
41
|
+
'rack.version' => [1,1],
|
|
42
|
+
'rack.url_scheme' => 'http', # ToDo: support https?
|
|
43
|
+
'rack.input' => Buffer.new,
|
|
44
|
+
'rack.errors' => $stdout, # ToDo: Sure about that?
|
|
45
|
+
'rack.multithread' => true,
|
|
46
|
+
'rack.multiprocess' => false,
|
|
47
|
+
'rack.run_once' => false,
|
|
48
|
+
# HTTP specific
|
|
49
|
+
'REQUEST_METHOD' => request.get_method.name,
|
|
50
|
+
'SCRIPT_NAME' => '',
|
|
51
|
+
'PATH_INFO' => query_string[0],
|
|
52
|
+
'QUERY_STRING' => if query_string[1] && query_string[1] != '' then "?#{query_string[1]}" else '' end,
|
|
53
|
+
'SERVER_NAME' => 'localhost', # ToDo: Be more precise!
|
|
54
|
+
'SERVER_PORT' => '8080', # ToDo: Be more precise!
|
|
55
|
+
# Thick specific
|
|
56
|
+
'thick.async' => AsyncResponse.new(context)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Get content length if available
|
|
60
|
+
length = Thick::Java::HttpHeaders.get_content_length(request)
|
|
61
|
+
@env['CONTENT_LENGTH'] = length if length > 0
|
|
62
|
+
|
|
63
|
+
# Extract headers and normalize it's names
|
|
64
|
+
request.header_names.each do |name|
|
|
65
|
+
rack_name = name.gsub('-', '_').upcase
|
|
66
|
+
rack_name = "HTTP_#{rack_name}" unless %w(CONTENT_TYPE CONTENT_LENGTH).include?(rack_name)
|
|
67
|
+
@env[rack_name] = request.get_header(name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Write request content into prepared IO
|
|
71
|
+
@env['rack.input'].write(request.content)
|
|
72
|
+
|
|
73
|
+
# No chunks expected, handle request
|
|
74
|
+
handle_request(context) unless request.chunked?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def on_chunk(context, chunk)
|
|
78
|
+
# Write request content into prepared IO
|
|
79
|
+
@env['rack.input'].write(chunk.content)
|
|
80
|
+
|
|
81
|
+
# No more chunks expected, handle request
|
|
82
|
+
handle_request(context) if chunk.last?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def handle_request(context)
|
|
86
|
+
response = @application.call(@env)
|
|
87
|
+
|
|
88
|
+
# Let the application make the response as it wants
|
|
89
|
+
if @env['thick.async'].custom?
|
|
90
|
+
@env['thick.async'].ready!
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Unpack response
|
|
95
|
+
status, headers, content = response
|
|
96
|
+
|
|
97
|
+
# Create response and set status as requested by application
|
|
98
|
+
@response = Thick::Java::DefaultHttpResponse.new(Thick::Java::HttpVersion::HTTP_1_1, Thick::Java::HttpResponseStatus.value_of(status.to_i))
|
|
99
|
+
|
|
100
|
+
# Set headers as requested by application
|
|
101
|
+
headers.each do |name, value|
|
|
102
|
+
Thick::Java::HttpHeaders.set_header(@response, name, value)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context.channel.write(@response)
|
|
106
|
+
|
|
107
|
+
# Set content as requested by application
|
|
108
|
+
case
|
|
109
|
+
when content.respond_to?(:to_path)
|
|
110
|
+
serve_file(context, content.to_path)
|
|
111
|
+
else
|
|
112
|
+
content.each do |chunk|
|
|
113
|
+
context.channel.write(Thick::Java::ChannelBuffers.copiedBuffer(chunk, Thick::Java::CharsetUtil::UTF_8))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# When content is closable, close it
|
|
118
|
+
content.close if content.respond_to?(:close)
|
|
119
|
+
|
|
120
|
+
# Trigger async handler and marker if async response requested, close connection otherwise
|
|
121
|
+
if @env['thick.async'].async?
|
|
122
|
+
@env['thick.async'].ready!
|
|
123
|
+
else
|
|
124
|
+
@env['thick.async'].close
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def channelClosed(context, event)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def exceptionCaught(context, e)
|
|
132
|
+
puts e.message if e.respond_to?(:message)
|
|
133
|
+
puts e.backtrace if e.respond_to?(:backtrace)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
data/lib/thick.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'rack'
|
|
2
|
+
|
|
3
|
+
require 'java'
|
|
4
|
+
|
|
5
|
+
require File.expand_path('../jars/netty-3.4.0.Alpha2.jar', __FILE__)
|
|
6
|
+
|
|
7
|
+
require 'thick/java'
|
|
8
|
+
require 'thick/version'
|
|
9
|
+
require 'thick/buffer'
|
|
10
|
+
require 'thick/async_response'
|
|
11
|
+
require 'thick/server_handler'
|
|
12
|
+
require 'thick/pipeline_factory'
|
|
13
|
+
require 'thick/server'
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: thick
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease:
|
|
5
|
+
version: 0.0.4
|
|
6
|
+
platform: java
|
|
7
|
+
authors:
|
|
8
|
+
- Marek Jelen
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
|
|
13
|
+
date: 2012-03-29 00:00:00 Z
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: rack
|
|
17
|
+
prerelease: false
|
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
19
|
+
none: false
|
|
20
|
+
requirements:
|
|
21
|
+
- - "="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 1.4.1
|
|
24
|
+
type: :runtime
|
|
25
|
+
version_requirements: *id001
|
|
26
|
+
description: Very lightweight web server for JRuby based on Netty library.
|
|
27
|
+
email:
|
|
28
|
+
- marek@jelen.biz
|
|
29
|
+
executables:
|
|
30
|
+
- thick
|
|
31
|
+
extensions: []
|
|
32
|
+
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
|
|
35
|
+
files:
|
|
36
|
+
- bin/thick
|
|
37
|
+
- lib/thick.rb
|
|
38
|
+
- lib/jars/netty-3.4.0.Alpha2.jar
|
|
39
|
+
- lib/thick/async_response.rb
|
|
40
|
+
- lib/thick/buffer.rb
|
|
41
|
+
- lib/thick/java.rb
|
|
42
|
+
- lib/thick/pipeline_factory.rb
|
|
43
|
+
- lib/thick/server.rb
|
|
44
|
+
- lib/thick/server_handler.rb
|
|
45
|
+
- lib/thick/version.rb
|
|
46
|
+
- LICENSE
|
|
47
|
+
- README.md
|
|
48
|
+
- ROADMAP.md
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
homepage: http://github.com/marekjelen/thick
|
|
51
|
+
licenses: []
|
|
52
|
+
|
|
53
|
+
post_install_message:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
none: false
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: "0"
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: "0"
|
|
70
|
+
requirements: []
|
|
71
|
+
|
|
72
|
+
rubyforge_project:
|
|
73
|
+
rubygems_version: 1.8.15
|
|
74
|
+
signing_key:
|
|
75
|
+
specification_version: 3
|
|
76
|
+
summary: Very lightweight web server for JRuby
|
|
77
|
+
test_files: []
|
|
78
|
+
|