reel-io 0.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/reel/io.rb +27 -0
- data/lib/reel/io/accessors.rb +55 -0
- data/lib/reel/io/connection.rb +95 -0
- data/lib/reel/io/eventsource.rb +6 -0
- data/lib/reel/io/singletons.rb +73 -0
- data/lib/reel/io/streamers.rb +65 -0
- data/lib/reel/io/websocket.rb +39 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b46155d8f78297c924151729c44bf1cbb654d797
|
4
|
+
data.tar.gz: fcdf2057bd1980f8c6be37dd418dc510ce92c12a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 10efb065e937a78f27c9af9b3fb7785d7d6ae663eae5c54df6105fa26006b6b16f9d869b2a606b7a9c96f767757de9a570e4769adc5426f086df7ef3b62dd2c0
|
7
|
+
data.tar.gz: 77e7c5a5c4f309e31ce4af0433fa19dd33b2e713b1582e985a18ba9b95dc6c3d108985bb1c21c936f3f149a92c50ef6f75de018e0f9f970b56e7c18da41beb3d
|
data/lib/reel/io.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'celluloid/current'
|
3
|
+
require 'websocket/driver'
|
4
|
+
|
5
|
+
module Reel::IO
|
6
|
+
require 'reel/io/singletons'
|
7
|
+
require 'reel/io/accessors'
|
8
|
+
require 'reel/io/connection'
|
9
|
+
require 'reel/io/websocket'
|
10
|
+
require 'reel/io/eventsource'
|
11
|
+
class << self
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators WebSocket, websocket?, websocket!
|
14
|
+
def_delegators EventSource, eventsource?, eventsource!
|
15
|
+
def_delegators Connection, persistent?, persistent!, :[], :[]=
|
16
|
+
end
|
17
|
+
|
18
|
+
class Server
|
19
|
+
#de TODO: Replace Reel internals with this module
|
20
|
+
end
|
21
|
+
|
22
|
+
class Client
|
23
|
+
#de TODO: Replace celluloid-websocket-client.
|
24
|
+
#de Same API goes either direction.
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Reel::IO::Accessors
|
2
|
+
HEADERS = {
|
3
|
+
'HTTP_ORIGIN' => 'Origin',
|
4
|
+
'HTTP_SEC_WEBSOCKET_KEY' => 'Sec-WebSocket-Key',
|
5
|
+
'HTTP_SEC_WEBSOCKET_KEY1' => 'Sec-WebSocket-Key1',
|
6
|
+
'HTTP_SEC_WEBSOCKET_KEY2' => 'Sec-WebSocket-Key2',
|
7
|
+
'HTTP_SEC_WEBSOCKET_EXTENSIONS' => 'Sec-WebSocket-Extensions',
|
8
|
+
'HTTP_SEC_WEBSOCKET_PROTOCOL' => 'Sec-WebSocket-Protocol',
|
9
|
+
'HTTP_SEC_WEBSOCKET_VERSION' => 'Sec-WebSocket-Version'
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
PORTABLE_HASH = Hash.new do |hash, key|
|
13
|
+
if key
|
14
|
+
result = hash.keys.find {|k| "#{k}" =~ /#{key}/i}
|
15
|
+
result = hash[HEADERS[key]] unless result
|
16
|
+
hash[result]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def context(env={})
|
21
|
+
@options = env.delete(:options) || {} unless @context
|
22
|
+
@context ||= PORTABLE_HASH.merge(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
def url
|
26
|
+
@url ||= "#{(secure?) ? 'wss' : 'ws'}://#{context[:HTTP_HOST]}#{context[:REQUEST_URI]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
HEADERS = {
|
30
|
+
'HTTP_ORIGIN' => 'Origin',
|
31
|
+
'HTTP_SEC_WEBSOCKET_KEY' => 'Sec-WebSocket-Key',
|
32
|
+
'HTTP_SEC_WEBSOCKET_KEY1' => 'Sec-WebSocket-Key1',
|
33
|
+
'HTTP_SEC_WEBSOCKET_KEY2' => 'Sec-WebSocket-Key2',
|
34
|
+
'HTTP_SEC_WEBSOCKET_EXTENSIONS' => 'Sec-WebSocket-Extensions',
|
35
|
+
'HTTP_SEC_WEBSOCKET_PROTOCOL' => 'Sec-WebSocket-Protocol',
|
36
|
+
'HTTP_SEC_WEBSOCKET_VERSION' => 'Sec-WebSocket-Version'
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
PORTABLE_HASH = Hash.new do |hash, key|
|
40
|
+
if key
|
41
|
+
result = hash.keys.find {|k| "#{k}" =~ /#{key}/i}
|
42
|
+
result = hash[HEADERS[key]] unless result
|
43
|
+
hash[result]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def context(env={})
|
48
|
+
@options = env.delete(:options) || {} unless @context
|
49
|
+
@context ||= PORTABLE_HASH.merge(env)
|
50
|
+
end
|
51
|
+
|
52
|
+
def url
|
53
|
+
@url ||= "#{(secure?) ? 'wss' : 'ws'}://#{context[:HTTP_HOST]}#{context[:REQUEST_URI]}"
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class Reel::IO::Connection
|
2
|
+
|
3
|
+
def websocket?
|
4
|
+
self.class == Reel::IO::WebSocket
|
5
|
+
end
|
6
|
+
|
7
|
+
def eventsource?
|
8
|
+
self.class == Reel::IO::EventSource
|
9
|
+
end
|
10
|
+
|
11
|
+
extend Reel::IO::Singletons::Connection
|
12
|
+
|
13
|
+
include Celluloid
|
14
|
+
include Reel::IO::Accessors
|
15
|
+
include Reel::IO::Streamers
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
attr_reader :io, :url
|
19
|
+
alias :env :context
|
20
|
+
alias :socket :io
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@polling = @options.fetch(:polling, :perpetual)
|
24
|
+
@autostart = @options.fetch(:autostart, true) #de Only if there are emitters:
|
25
|
+
@emitters = @options.fetch(:emitters, {})
|
26
|
+
@message_buffer = []
|
27
|
+
@driver.on(:message) do |message|
|
28
|
+
@message_buffer.push(message.data)
|
29
|
+
end
|
30
|
+
@driver.on(:close) do
|
31
|
+
@io.close
|
32
|
+
end
|
33
|
+
@driver.start
|
34
|
+
|
35
|
+
@io ||= begin
|
36
|
+
context_suffixed(:hijack).call
|
37
|
+
context_suffixed(:hijack_io)
|
38
|
+
end
|
39
|
+
|
40
|
+
@io = Celluloid::IO::TCPSocket.new(@io)
|
41
|
+
|
42
|
+
if @emitters.any?
|
43
|
+
@emitters.each { |type, proc|
|
44
|
+
@driver.on(type, proc)
|
45
|
+
}
|
46
|
+
run if @autostart
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :socket
|
51
|
+
def_delegators :@io, :addr, :peeraddr
|
52
|
+
def_delegators :@driver, :ping, :on
|
53
|
+
|
54
|
+
def secure?
|
55
|
+
@secure ||= (@url && @url.start_with?('wss://')) ||
|
56
|
+
@context['HTTP_PORT'] == 443 ||
|
57
|
+
@context['HTTPS'] == true ||
|
58
|
+
@contex.select { |k,v| k.downcase.end_with?("scheme") && v.downcase == 'https' } > 0 ||
|
59
|
+
end
|
60
|
+
|
61
|
+
def closed?
|
62
|
+
@io.closed?
|
63
|
+
end
|
64
|
+
|
65
|
+
def close
|
66
|
+
@driver.close
|
67
|
+
@io.close
|
68
|
+
rescue
|
69
|
+
end
|
70
|
+
|
71
|
+
def context_suffixed(*key)
|
72
|
+
return key.find { |k| context_suffixed(k) } unless key.one?
|
73
|
+
key = "#{key.first}".downcase
|
74
|
+
context.find { |k, v| "#{k}".end_with?(key) && v }
|
75
|
+
end
|
76
|
+
|
77
|
+
def cancel_timer!
|
78
|
+
@timer && @timer.cancel
|
79
|
+
end
|
80
|
+
|
81
|
+
def server?
|
82
|
+
@mode == :server
|
83
|
+
end
|
84
|
+
|
85
|
+
def client?
|
86
|
+
@mode == :client
|
87
|
+
end
|
88
|
+
|
89
|
+
[:message, :error, :close, :ping, :pong].each do |meth|
|
90
|
+
define_method "on_#{meth}" do |&proc|
|
91
|
+
@driver.send(:on, meth, &proc)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Reel::IO::Singletons
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
def websocket!(env, io=nil)
|
5
|
+
Reel::IO::WebSocket.new(env, io)
|
6
|
+
end
|
7
|
+
alias :[]= :websocket!
|
8
|
+
alias :[] :websocket!
|
9
|
+
|
10
|
+
def websocket?(env)
|
11
|
+
return false unless env['REQUEST_METHOD'] == 'GET'
|
12
|
+
connection, upgrade = Reel::IO::Singletons::Connection.header(env, :connection, :upgrade)
|
13
|
+
connection.downcase.split(/ *, */).include?('upgrade') and
|
14
|
+
upgrade.downcase == 'websocket'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module EventSource
|
19
|
+
def eventsource!(env, io=nil)
|
20
|
+
Reel::IO::EventSource.new(env, io)
|
21
|
+
end
|
22
|
+
alias :[]= :eventsource!
|
23
|
+
alias :[] :eventsource!
|
24
|
+
def eventsource?(env)
|
25
|
+
return false unless env['REQUEST_METHOD'] == 'GET'
|
26
|
+
Reel::IO::Singletons::Connection.header(env, :accept)
|
27
|
+
.downcase.split(/\s*,\s*/)
|
28
|
+
.include?('text/event-stream')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Connection
|
33
|
+
|
34
|
+
extend Forwardable
|
35
|
+
def_delegators Reel::IO::WebSocket,
|
36
|
+
:websocket!,
|
37
|
+
:websocket?
|
38
|
+
|
39
|
+
def_delegators Reel::IO::EventSource,
|
40
|
+
:eventsource!,
|
41
|
+
:eventsource?
|
42
|
+
|
43
|
+
def []=(env)
|
44
|
+
persistent!(env)
|
45
|
+
end
|
46
|
+
|
47
|
+
def []=(env, io)
|
48
|
+
persistent!(env, io)
|
49
|
+
end
|
50
|
+
|
51
|
+
def persistent!(env, io=nil)
|
52
|
+
return unless persistent?(env)
|
53
|
+
return websocket!(env, io) if websocket?(env)
|
54
|
+
eventsource!(env, io)
|
55
|
+
end
|
56
|
+
def :connection! :persistent!
|
57
|
+
def :channel! :persistent!
|
58
|
+
|
59
|
+
def persistent?(env)
|
60
|
+
websocket?(env) || eventsource?(env)
|
61
|
+
end
|
62
|
+
|
63
|
+
def header(env, *key)
|
64
|
+
if key.length > 1
|
65
|
+
return key.map { |k| header(env, k) }
|
66
|
+
else
|
67
|
+
key = key.first
|
68
|
+
end
|
69
|
+
[ "HTTP_#{key}".upcase, "#{key}".capitalize ].find { |k| !env[k].nil? } || ''
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Reel::IO::Streamers
|
2
|
+
|
3
|
+
def run
|
4
|
+
(polling?) ? async.reading! || async.read && read_every(@polling)
|
5
|
+
end
|
6
|
+
|
7
|
+
alias :run! :run
|
8
|
+
alias :poll :run
|
9
|
+
alias :poll! :run
|
10
|
+
alias :start :run
|
11
|
+
alias :start! :run
|
12
|
+
alias :go :run
|
13
|
+
alias :go! :run
|
14
|
+
|
15
|
+
def reading!
|
16
|
+
loop {
|
17
|
+
read
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def perpetual?
|
22
|
+
@polling == :perpetual
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(msg)
|
26
|
+
if msg.is_a? String
|
27
|
+
@driver.text(msg)
|
28
|
+
elsif msg.is_a? Array
|
29
|
+
@driver.binary(msg)
|
30
|
+
else
|
31
|
+
raise "Can only send byte array or string over driver."
|
32
|
+
end
|
33
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
34
|
+
cancel_timer!
|
35
|
+
raise SocketError, "error writing to socket"
|
36
|
+
rescue
|
37
|
+
cancel_timer!
|
38
|
+
raise
|
39
|
+
end
|
40
|
+
alias_method :<<, :write
|
41
|
+
|
42
|
+
def read
|
43
|
+
while @message_buffer.empty?
|
44
|
+
buffer = @io.readpartial(Connection::BUFFER_SIZE)
|
45
|
+
@driver.parse(buffer)
|
46
|
+
end
|
47
|
+
@message_buffer.shift
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_every(n, unit = :s)
|
51
|
+
cancel_timer! # only one timer allowed per stream
|
52
|
+
seconds = case unit.to_s
|
53
|
+
when /\Am/
|
54
|
+
n * 60
|
55
|
+
when /\Ah/
|
56
|
+
n * 3600
|
57
|
+
else
|
58
|
+
n
|
59
|
+
end
|
60
|
+
@timer = every(seconds) { read }
|
61
|
+
end
|
62
|
+
alias read_interval read_every
|
63
|
+
alias read_frequency read_every
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Reel::IO::WebSocket < Reel::IO::Connection
|
2
|
+
|
3
|
+
extend Reel::IO::Singletons::WebSocket
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
if args.last.respond_to? :to_io
|
7
|
+
@mode = :server
|
8
|
+
@io = args.pop
|
9
|
+
end
|
10
|
+
|
11
|
+
if args.first.is_a? Hash
|
12
|
+
env = args.shift
|
13
|
+
@url = env.delete(:connect)
|
14
|
+
elsif args.first.is_a? String
|
15
|
+
@mode = :client
|
16
|
+
@url = args.shift
|
17
|
+
end
|
18
|
+
|
19
|
+
raise ArgumentError unless args.empty? && (env.is_a?(Hash) || @url.is_a?(String) && !@url.empty?)
|
20
|
+
@mode ||= (@url) ? :client : :server
|
21
|
+
context(env)
|
22
|
+
|
23
|
+
@driver = if server?
|
24
|
+
if context[:HTTP_SEC_WEBSOCKET_VERSION]
|
25
|
+
::WebSocket::Hybi.new(Celluloid::Actor.curent, @options.merge(require_masking: true))
|
26
|
+
elsif context[:HTTP_SEC_WEBSOCKET_KEY1]
|
27
|
+
::WebSocket::Draft76.new(Celluloid::Actor.curent, @options)
|
28
|
+
else
|
29
|
+
::WebSocket::Draft75.new(Celluloid::Actor.curent, @options)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
::WebSocket::Client.new(Celluloid::Actor.current, @options.merge(masking: true))
|
33
|
+
end
|
34
|
+
|
35
|
+
super()
|
36
|
+
rescue EOFError
|
37
|
+
close
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reel-io
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- digitalextremist //
|
8
|
+
- Tony Arcieri
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-07-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.4
|
20
|
+
name: websocket-driver
|
21
|
+
prerelease: false
|
22
|
+
type: :runtime
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.5.4
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.17.0
|
34
|
+
name: celluloid
|
35
|
+
prerelease: false
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.17.0
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.16.5.pre0
|
48
|
+
name: celluloid-io
|
49
|
+
prerelease: false
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.16.5.pre0
|
56
|
+
description: Evented WebSockets and Server Side Events for Ruby, using Celluloid::IO and WebSocket::Driver.
|
57
|
+
email:
|
58
|
+
- code@extremist.digital
|
59
|
+
- tony.arcieri@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- lib/reel/io.rb
|
65
|
+
- lib/reel/io/accessors.rb
|
66
|
+
- lib/reel/io/connection.rb
|
67
|
+
- lib/reel/io/eventsource.rb
|
68
|
+
- lib/reel/io/singletons.rb
|
69
|
+
- lib/reel/io/streamers.rb
|
70
|
+
- lib/reel/io/websocket.rb
|
71
|
+
homepage: https://github.com/celluloid/reel-io
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>'
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 1.3.1
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.4.8
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Simple, non-blocking persistent I/O for HTTP/S servers.
|
95
|
+
test_files: []
|