sloth-reel 0.1.0
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.
- 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
|