sloth-reel 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/README.adoc +65 -0
- data/README.ja.adoc +65 -0
- data/Rakefile +96 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/reel/rack/server.rb +127 -0
- data/lib/reel/server.rb +78 -0
- data/lib/reel/websocket.rb +132 -0
- data/lib/sloth/reel.rb +13 -0
- data/lib/sloth/reel/sinatra.rb +18 -0
- data/lib/sloth/reel/version.rb +5 -0
- data/sample/rack_server_hello_1.rb +74 -0
- data/sample/server_http_hello_1.rb +84 -0
- data/sample/server_http_ws_chat_1.rb +105 -0
- data/sample/server_http_ws_time_1.rb +94 -0
- data/sample/server_sinatra_hello_1.rb +32 -0
- data/sample/server_sinatra_hello_2.rb +36 -0
- data/sample/server_sinatra_hello_3.rb +45 -0
- data/sample/server_sinatra_hello_4.rb +44 -0
- data/sample/server_sinatra_ws_chat_1.rb +68 -0
- data/sample/server_sinatra_ws_time_1.rb +55 -0
- data/sample/server_sinatra_ws_time_2.rb +63 -0
- data/sloth-reel.gemspec +33 -0
- metadata +224 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 706dfba4a0493e2f17b2eaddbe2ff554bec480daf299e594b7ef6097bc93dbd5
|
4
|
+
data.tar.gz: 26d80753d69cadacf314de7f84ae7e14fb3b2d42d095b6ea4ee7b043c8e815a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d209cf1c4e7513e622654073940802ae537da1af7225bae0ebe5bb1ccbfe3041eb89c50cdfbf56a39639fa9ebadbe29d5921240e37148618885d61c5572efa75
|
7
|
+
data.tar.gz: d3f2e8fde962829942ccc4f9035cdeae2febedac7ceb60d1ace350cabf8b18eacd0d81d4a69afafaee159ed18260149dbad49cf3866a34b744bb7d0abffac4ee
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
*.bundle
|
11
|
+
*.so
|
12
|
+
*.o
|
13
|
+
*.a
|
14
|
+
mkmf.log
|
15
|
+
/vendor/
|
16
|
+
*.pid
|
17
|
+
*.log
|
18
|
+
*.log.*
|
19
|
+
/var/
|
20
|
+
/log/
|
21
|
+
*.swp
|
22
|
+
/.rspec_status
|
23
|
+
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.adoc
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= Reels
|
2
|
+
|
3
|
+
Httpd and WebSocket sloth framework based on Celluloid, Reel, Rack and Sinatra.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
* Handle Sinatra asynchronously.
|
8
|
+
* Coordinate Sinatra and WebSocket.
|
9
|
+
* The implementation is a monkey patch to Reel, Sinatra and WebSocket.
|
10
|
+
* It is not a stable version because it depends on the celluloid development version.
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
[source,ruby]
|
17
|
+
----
|
18
|
+
gem 'sloth-reel'
|
19
|
+
----
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle install
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install sloth-reel
|
28
|
+
or
|
29
|
+
$ gem install -l sloth-reel-x.x.x.gem
|
30
|
+
|
31
|
+
== Usage
|
32
|
+
|
33
|
+
=== Example 1
|
34
|
+
|
35
|
+
[source,ruby]
|
36
|
+
----
|
37
|
+
require 'sloth/reel'
|
38
|
+
|
39
|
+
class WebApp < Sinatra::Base
|
40
|
+
get "/" do
|
41
|
+
'<html> <body> <form method="POST"> <input type="submit" value="Hello." /> </form> </body> </html>'
|
42
|
+
end
|
43
|
+
|
44
|
+
post "/" do
|
45
|
+
'<html> <body> Howdy. </body> </html>'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Reel::Rack::Server.new( WebApp.new, Host: "0.0.0.0", Port: 3000 )
|
50
|
+
|
51
|
+
sleep
|
52
|
+
----
|
53
|
+
|
54
|
+
== Reference
|
55
|
+
|
56
|
+
|
57
|
+
== Contributing
|
58
|
+
|
59
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/arimay/sloth-reel.
|
60
|
+
|
61
|
+
== License
|
62
|
+
|
63
|
+
The gem is available as open source under the terms of the http://opensource.org/licenses/MIT[MIT License].
|
64
|
+
|
65
|
+
Copyright (c) ARIMA Yasuhiro <arima.yasuhiro@gmail.com>
|
data/README.ja.adoc
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= Reels
|
2
|
+
|
3
|
+
Httpd と WebSocket のナマケモノフレームワーク. Celluloid, Reel, Rack, Sinatra に基づく.
|
4
|
+
|
5
|
+
== 特徴
|
6
|
+
|
7
|
+
* 非同期で Sinatra を扱う.
|
8
|
+
* Sinatra と WebSocket を協調させる.
|
9
|
+
* 実装は Reel、Sinatra、および WebSocket へのモンキーパッチ.
|
10
|
+
* Celluloid 開発版に依存するため、安定版ではない。
|
11
|
+
|
12
|
+
== 導入
|
13
|
+
|
14
|
+
アプリの Gemfile にこの行を追加
|
15
|
+
|
16
|
+
[source,ruby]
|
17
|
+
----
|
18
|
+
gem 'sloth-reel'
|
19
|
+
----
|
20
|
+
|
21
|
+
それから実行
|
22
|
+
|
23
|
+
$ bundle install
|
24
|
+
|
25
|
+
または次のように手動で導入
|
26
|
+
|
27
|
+
$ gem install sloth-reel
|
28
|
+
or
|
29
|
+
$ gem install -l sloth-reel-x.x.x.gem
|
30
|
+
|
31
|
+
== 使い方
|
32
|
+
|
33
|
+
=== Example 1
|
34
|
+
|
35
|
+
[source,ruby]
|
36
|
+
----
|
37
|
+
require 'sloth/reel'
|
38
|
+
|
39
|
+
class WebApp < Sinatra::Base
|
40
|
+
get "/" do
|
41
|
+
'<html> <body> <form method="POST"> <input type="submit" value="Hello." /> </form> </body> </html>'
|
42
|
+
end
|
43
|
+
|
44
|
+
post "/" do
|
45
|
+
'<html> <body> Howdy. </body> </html>'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Reel::Rack::Server.new( WebApp.new, Host: "0.0.0.0", Port: 3000 )
|
50
|
+
|
51
|
+
sleep
|
52
|
+
----
|
53
|
+
|
54
|
+
== リファレンス
|
55
|
+
|
56
|
+
|
57
|
+
== 貢献
|
58
|
+
|
59
|
+
不具合報告とプルリクエストは GitHub https://github.com/arimay/sloth-reel まで.
|
60
|
+
|
61
|
+
== ライセンス
|
62
|
+
|
63
|
+
この Gem は、 http://opensource.org/licenses/MIT[MITライセンス] の条件に基づいてオープンソースとして入手できる.
|
64
|
+
|
65
|
+
Copyright (c) ARIMA Yasuhiro <arima.yasuhiro@gmail.com>
|
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "bundler/gem_helper"
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
class Bundler::GemHelper
|
10
|
+
|
11
|
+
def git_archive( dir = "../zip" )
|
12
|
+
FileUtils.mkdir_p dir
|
13
|
+
dest_path = File.join(dir, "#{name}-#{version}.zip")
|
14
|
+
cmnd = "git archive --format zip --prefix=#{name}/ HEAD > #{dest_path}"
|
15
|
+
|
16
|
+
_, code = sh_with_status( cmnd )
|
17
|
+
raise "Couldn't archive gem," unless code == 0
|
18
|
+
|
19
|
+
Bundler.ui.confirm "#{name} #{version} archived to #{dest_path}."
|
20
|
+
end
|
21
|
+
|
22
|
+
def git_push
|
23
|
+
ver = version.to_s
|
24
|
+
|
25
|
+
cmnd = "git push origin #{ver} "
|
26
|
+
_, code = sh_with_status( cmnd )
|
27
|
+
raise "Couldn't git push origin." unless code == 0
|
28
|
+
|
29
|
+
cmnd = "git push "
|
30
|
+
_, code = sh_with_status( cmnd )
|
31
|
+
raise "Couldn't git push." unless code == 0
|
32
|
+
|
33
|
+
Bundler.ui.confirm "Git Push #{ver}."
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_version( new_version )
|
37
|
+
version_filename = %x[ find . -type f -name "version.rb" | grep -v vendor | head -1 ].chomp
|
38
|
+
version_pathname = File.expand_path( version_filename )
|
39
|
+
lines = File.open( version_pathname ).read
|
40
|
+
lines = lines.gsub( /VERSION\s*=\s*\"\d+\.\d+\.\d+\"/, "VERSION = \"#{new_version}\"" )
|
41
|
+
File.open( version_pathname, "w" ) do |file|
|
42
|
+
file.write( lines )
|
43
|
+
end
|
44
|
+
|
45
|
+
cmnd = "git add #{version_pathname} "
|
46
|
+
_, code = sh_with_status( cmnd )
|
47
|
+
raise "Couldn't git add," unless code == 0
|
48
|
+
|
49
|
+
cmnd = "git commit -m '#{new_version}' "
|
50
|
+
_, code = sh_with_status( cmnd )
|
51
|
+
raise "Couldn't git commit." unless code == 0
|
52
|
+
|
53
|
+
cmnd = "git tag #{new_version} "
|
54
|
+
_, code = sh_with_status( cmnd )
|
55
|
+
raise "Couldn't git tag." unless code == 0
|
56
|
+
|
57
|
+
Bundler.ui.confirm "Update Tags to #{new_version}."
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
Bundler::GemHelper.new(Dir.pwd).instance_eval do
|
63
|
+
|
64
|
+
desc "Archive #{name}-#{version}.zip from repository"
|
65
|
+
task 'zip' do
|
66
|
+
git_archive
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "Git Push"
|
70
|
+
task 'push' do
|
71
|
+
git_push
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Update Version Tiny"
|
75
|
+
task 'tiny' do
|
76
|
+
major, minor, tiny = version.to_s.split('.')
|
77
|
+
new_version = [major, minor, tiny.to_i + 1].join('.')
|
78
|
+
update_version( new_version )
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Update Version Minor"
|
82
|
+
task 'minor' do
|
83
|
+
major, minor, _tiny = version.to_s.split('.')
|
84
|
+
new_version = [major, minor.to_i + 1, 0].join('.')
|
85
|
+
update_version( new_version )
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Update Version Major"
|
89
|
+
task 'major' do
|
90
|
+
major, _minor, _tiny = version.to_s.split('.')
|
91
|
+
new_version = [major.to_i + 1, 0, 0].join('.')
|
92
|
+
update_version( new_version )
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "sloth/reel"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Adapted from code orinially Copyright (c) 2013 Jonathan Stott
|
2
|
+
|
3
|
+
require 'reel'
|
4
|
+
require 'rack'
|
5
|
+
|
6
|
+
module Reel
|
7
|
+
module Rack
|
8
|
+
class Server < Reel::Server::HTTP
|
9
|
+
include Celluloid::Internals::Logger
|
10
|
+
|
11
|
+
attr_reader :app
|
12
|
+
|
13
|
+
def initialize(app, options)
|
14
|
+
raise ArgumentError, "no host given" unless options[:Host]
|
15
|
+
raise ArgumentError, "no port given" unless options[:Port]
|
16
|
+
|
17
|
+
info "A Reel good HTTP server! (Codename \"#{::Reel::CODENAME}\")"
|
18
|
+
info "Listening on http://#{options[:Host]}:#{options[:Port]}"
|
19
|
+
|
20
|
+
super(options[:Host], options[:Port], options, &method(:on_connection))
|
21
|
+
@app = app
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_connection(connection)
|
25
|
+
connection.each_request do |request|
|
26
|
+
if request.websocket?
|
27
|
+
connection.detach
|
28
|
+
route_websocket request
|
29
|
+
return
|
30
|
+
else
|
31
|
+
route_request request
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def route_websocket( request )
|
37
|
+
options = {
|
38
|
+
:method => request.method,
|
39
|
+
:input => request.body.to_s,
|
40
|
+
"websocket" => request.websocket,
|
41
|
+
}.merge(convert_headers(request.headers))
|
42
|
+
|
43
|
+
normalize_env(options)
|
44
|
+
|
45
|
+
app.call ::Rack::MockRequest.env_for(request.url, options)
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Compile the regex once
|
50
|
+
CONTENT_LENGTH_HEADER = %r{^content-length$}i
|
51
|
+
|
52
|
+
def route_request(request)
|
53
|
+
options = {
|
54
|
+
:method => request.method,
|
55
|
+
:input => request.body.to_s,
|
56
|
+
"REMOTE_ADDR" => request.remote_addr
|
57
|
+
}.merge(convert_headers(request.headers))
|
58
|
+
|
59
|
+
normalize_env(options)
|
60
|
+
|
61
|
+
status, headers, body = app.call ::Rack::MockRequest.env_for(request.url, options)
|
62
|
+
|
63
|
+
if body.respond_to? :each
|
64
|
+
# If Content-Length was specified we can send the response all at once
|
65
|
+
if headers.keys.detect { |h| h =~ CONTENT_LENGTH_HEADER }
|
66
|
+
# Can't use collect here because Rack::BodyProxy/Rack::Lint isn't a real Enumerable
|
67
|
+
full_body = ''
|
68
|
+
body.each { |b| full_body << b }
|
69
|
+
request.respond status_symbol(status), headers, full_body
|
70
|
+
else
|
71
|
+
request.respond status_symbol(status), headers.merge(:transfer_encoding => :chunked)
|
72
|
+
body.each { |chunk| request << chunk }
|
73
|
+
request.finish_response
|
74
|
+
end
|
75
|
+
else
|
76
|
+
Logger.error("don't know how to render: #{body.inspect}")
|
77
|
+
request.respond :internal_server_error, "An error occurred processing your request"
|
78
|
+
end
|
79
|
+
|
80
|
+
body.close if body.respond_to? :close
|
81
|
+
end
|
82
|
+
|
83
|
+
# Those headers must not start with 'HTTP_'.
|
84
|
+
NO_PREFIX_HEADERS=%w[CONTENT_TYPE CONTENT_LENGTH].freeze
|
85
|
+
|
86
|
+
def convert_headers(headers)
|
87
|
+
Hash[headers.map { |key, value|
|
88
|
+
header = key.upcase.gsub('-','_')
|
89
|
+
|
90
|
+
if NO_PREFIX_HEADERS.member?(header)
|
91
|
+
[header, value]
|
92
|
+
else
|
93
|
+
['HTTP_' + header, value]
|
94
|
+
end
|
95
|
+
}]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Copied from lib/puma/server.rb
|
99
|
+
def normalize_env(env)
|
100
|
+
if host = env["HTTP_HOST"]
|
101
|
+
if colon = host.index(":")
|
102
|
+
env["SERVER_NAME"] = host[0, colon]
|
103
|
+
env["SERVER_PORT"] = host[colon+1, host.bytesize]
|
104
|
+
else
|
105
|
+
env["SERVER_NAME"] = host
|
106
|
+
env["SERVER_PORT"] = default_server_port(env)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
env["SERVER_NAME"] = "localhost"
|
110
|
+
env["SERVER_PORT"] = default_server_port(env)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def default_server_port(env)
|
115
|
+
env['HTTP_X_FORWARDED_PROTO'] == 'https' ? 443 : 80
|
116
|
+
end
|
117
|
+
|
118
|
+
def status_symbol(status)
|
119
|
+
if status.is_a?(Integer)
|
120
|
+
Reel::Response::STATUS_CODES[status].downcase.gsub(/\s|-/, '_').to_sym
|
121
|
+
else
|
122
|
+
status.to_sym
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/reel/server.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Reel
|
2
|
+
# Base class for Reel servers.
|
3
|
+
#
|
4
|
+
# This class is a Celluloid::IO actor which provides a barebones server
|
5
|
+
# which does not open a socket itself, it just begin handling connections once
|
6
|
+
# initialized with a specific kind of protocol-based server.
|
7
|
+
|
8
|
+
# For specific protocol support, use:
|
9
|
+
|
10
|
+
# Reel::Server::HTTP
|
11
|
+
# Reel::Server::HTTPS
|
12
|
+
# Coming soon: Reel::Server::UNIX
|
13
|
+
|
14
|
+
class Server
|
15
|
+
include Celluloid::IO
|
16
|
+
# How many connections to backlog in the TCP accept queue
|
17
|
+
DEFAULT_BACKLOG = 100
|
18
|
+
MAX_CONNECTION = 16
|
19
|
+
|
20
|
+
execute_block_on_receiver :initialize
|
21
|
+
finalizer :shutdown
|
22
|
+
|
23
|
+
def initialize(server, options={}, &callback)
|
24
|
+
@spy = STDOUT if options[:spy]
|
25
|
+
@options = options
|
26
|
+
@callback = callback
|
27
|
+
@server = server
|
28
|
+
@max_connection = options[:max_connection] || MAX_CONNECTION
|
29
|
+
|
30
|
+
@server.listen(options.fetch(:backlog, DEFAULT_BACKLOG))
|
31
|
+
|
32
|
+
async.run
|
33
|
+
end
|
34
|
+
|
35
|
+
def shutdown
|
36
|
+
@server.close if @server
|
37
|
+
info "Terminate."
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
queue = Queue.new
|
42
|
+
(1..@max_connection).each do
|
43
|
+
Thread.start do
|
44
|
+
Thread.current.report_on_exception = false rescue nil
|
45
|
+
while sock = queue.pop
|
46
|
+
handle_connection sock
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
info "Ready."
|
51
|
+
loop do
|
52
|
+
queue.push @server.accept
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
info "Terminate."
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_connection(socket)
|
59
|
+
if @spy
|
60
|
+
require 'reel/spy'
|
61
|
+
socket = Reel::Spy.new(socket, @spy)
|
62
|
+
end
|
63
|
+
|
64
|
+
connection = Connection.new(socket)
|
65
|
+
|
66
|
+
begin
|
67
|
+
@callback.call(connection)
|
68
|
+
ensure
|
69
|
+
if connection.attached?
|
70
|
+
connection.close rescue nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
rescue RequestError, EOFError
|
74
|
+
# Client disconnected prematurely
|
75
|
+
# TODO: log this?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|