tubesock 0.1.2 → 0.1.3
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 +4 -4
- data/.travis.yml +7 -0
- data/Gemfile +1 -0
- data/README.md +3 -1
- data/Rakefile +9 -0
- data/lib/tubesock.rb +25 -23
- data/lib/tubesock/hijack.rb +33 -19
- data/lib/tubesock/version.rb +1 -1
- data/test/test_helper.rb +83 -0
- data/test/tubesock_test.rb +57 -0
- data/tubesock.gemspec +4 -1
- metadata +51 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0a5fc91c6e49797a1595e75ac1fa8425259370a3
|
|
4
|
+
data.tar.gz: 3af86ddcbe0fe0cf80aac51d25f27420ca2bb444
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b6fa82ff09d335de5942b2ee415a328c15a40ba579f82f1f1293dd6c40de9edb100eb633384d1debb7cde05c017ff69239e9ceff8da37bf8f8c583a9338757aa
|
|
7
|
+
data.tar.gz: 3b5050954b14fafc27df74ddaabb5a34f3e8399db95e3d256f5b71c14385f803a28a552a026fd679c2c3ec5e43408b9b0c3881806343e1dd2cc5129edf9d3f1f
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Tubesock
|
|
2
2
|
|
|
3
|
+
[](https://travis-ci.org/ngauthier/tubesock) [](https://codeclimate.com/github/ngauthier/tubesock)
|
|
4
|
+
|
|
3
5
|
Tubesock lets you use websockets from rack and rails 4+ by using Rack's new hijack interface to access the underlying socket connection.
|
|
4
6
|
|
|
5
7
|
In contrast to other websocket libraries, Tubesock does not use a reactor (read: no eventmachine). Instead, it leverages Rails 4's new full-stack concurrency support. Note that this means you must use a concurrent server. We recommend Puma.
|
|
@@ -12,7 +14,7 @@ Add this line to your application's Gemfile:
|
|
|
12
14
|
gem 'tubesock'
|
|
13
15
|
# currently, the ability to setup a websocket from rack is only
|
|
14
16
|
# available on my fork. If/when the PR is merged this will become a gem dependency
|
|
15
|
-
gem 'websocket', github: "ngauthier/websocket-ruby"
|
|
17
|
+
gem 'websocket', github: "ngauthier/websocket-ruby"
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
And then execute:
|
data/Rakefile
CHANGED
data/lib/tubesock.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
require "tubesock/version"
|
|
2
|
-
require "tubesock/hijack" if defined?(
|
|
2
|
+
require "tubesock/hijack" if defined?(ActiveSupport)
|
|
3
|
+
require "websocket"
|
|
3
4
|
|
|
5
|
+
# Easily interact with WebSocket connections over Rack.
|
|
6
|
+
# TODO: Example with pure Rack
|
|
4
7
|
class Tubesock
|
|
5
8
|
class HijackNotAvailable < RuntimeError
|
|
6
9
|
end
|
|
@@ -30,15 +33,12 @@ class Tubesock
|
|
|
30
33
|
end
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
def self.websocket?(env)
|
|
34
|
-
env['REQUEST_METHOD'] == 'GET' and
|
|
35
|
-
env['HTTP_CONNECTION'] and
|
|
36
|
-
env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
|
|
37
|
-
env['HTTP_UPGRADE'].downcase == 'websocket'
|
|
38
|
-
end
|
|
39
|
-
|
|
40
36
|
def send_data data, type = :text
|
|
41
|
-
frame = WebSocket::Frame::Outgoing::Server.new(
|
|
37
|
+
frame = WebSocket::Frame::Outgoing::Server.new(
|
|
38
|
+
version: @version,
|
|
39
|
+
data: JSON.dump(data),
|
|
40
|
+
type: type
|
|
41
|
+
)
|
|
42
42
|
@socket.write frame.to_s
|
|
43
43
|
rescue IOError
|
|
44
44
|
close
|
|
@@ -59,24 +59,26 @@ class Tubesock
|
|
|
59
59
|
def listen
|
|
60
60
|
Thread.new do
|
|
61
61
|
Thread.current.abort_on_exception = true
|
|
62
|
-
framebuffer = WebSocket::Frame::Incoming::Server.new(version: @version)
|
|
63
|
-
running = true
|
|
64
62
|
@open_handlers.each(&:call)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
running = false if data == ""
|
|
68
|
-
framebuffer << data
|
|
69
|
-
while frame = framebuffer.next
|
|
70
|
-
data = frame.data
|
|
71
|
-
if data == ""
|
|
72
|
-
running = false
|
|
73
|
-
else
|
|
74
|
-
@message_handlers.each{|h| h.call(HashWithIndifferentAccess.new(JSON.load(data))) }
|
|
75
|
-
end
|
|
76
|
-
end
|
|
63
|
+
each_frame do |data|
|
|
64
|
+
@message_handlers.each{|h| h.call(JSON.load(data)) }
|
|
77
65
|
end
|
|
78
66
|
@close_handlers.each(&:call)
|
|
79
67
|
@socket.close
|
|
80
68
|
end
|
|
81
69
|
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
def each_frame
|
|
73
|
+
framebuffer = WebSocket::Frame::Incoming::Server.new(version: @version)
|
|
74
|
+
while !@socket.closed?
|
|
75
|
+
data, addrinfo = @socket.recvfrom(2000)
|
|
76
|
+
break if data.empty?
|
|
77
|
+
framebuffer << data
|
|
78
|
+
while frame = framebuffer.next
|
|
79
|
+
return if frame.type == :close
|
|
80
|
+
yield frame.data
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
82
84
|
end
|
data/lib/tubesock/hijack.rb
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
require 'active_support/concern'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
# Interact with WebSocket connections in rails.
|
|
3
|
+
# Example:
|
|
4
|
+
# # The widget controller's `show` lets you edit a widget in
|
|
5
|
+
# # real time. The websocket frames from the client should be in JSON, for
|
|
6
|
+
# # example:
|
|
7
|
+
# # socket.send(JSON.stringify({name: "foo"}));
|
|
8
|
+
# class WidgetController < ApplicationController
|
|
9
|
+
# include Tubesock::Hijack
|
|
10
|
+
# def show
|
|
11
|
+
# widget = Widget.find params[:id]
|
|
12
|
+
# hijack do |tubesock|
|
|
13
|
+
# tubesock.onopen do
|
|
14
|
+
# widget.update_attribute :editing, true
|
|
15
|
+
# end
|
|
16
|
+
# tubesock.onmessage do |message|
|
|
17
|
+
# widget.update_attribute :name, message["name"]
|
|
18
|
+
# end
|
|
19
|
+
# tubesock.onclose do
|
|
20
|
+
# widget.update_attribute :editing, true
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
module Tubesock::Hijack
|
|
26
|
+
extend ActiveSupport::Concern
|
|
5
27
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ActiveRecord::Base.clear_active_connections!
|
|
13
|
-
end
|
|
14
|
-
sock.listen
|
|
15
|
-
render text: nil, status: -1
|
|
16
|
-
else
|
|
17
|
-
render text: "Not found", status: :not_found
|
|
18
|
-
end
|
|
28
|
+
included do
|
|
29
|
+
def hijack
|
|
30
|
+
sock = Tubesock.hijack(env)
|
|
31
|
+
yield sock
|
|
32
|
+
sock.onclose do
|
|
33
|
+
ActiveRecord::Base.clear_active_connections! if defined? ActiveRecord
|
|
19
34
|
end
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
module ClassMethods
|
|
35
|
+
sock.listen
|
|
36
|
+
render text: nil, status: -1
|
|
23
37
|
end
|
|
24
38
|
end
|
|
25
39
|
end
|
data/lib/tubesock/version.rb
CHANGED
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'simplecov'
|
|
3
|
+
SimpleCov.start
|
|
4
|
+
require 'active_support'
|
|
5
|
+
require 'minitest/autorun'
|
|
6
|
+
require 'webrick'
|
|
7
|
+
require 'stringio'
|
|
8
|
+
require 'tubesock'
|
|
9
|
+
require 'json'
|
|
10
|
+
|
|
11
|
+
class Tubesock::TestCase < Minitest::Unit::TestCase
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Abstract away frames and sockets so that
|
|
15
|
+
# tests can focus on message interaction
|
|
16
|
+
class Tubesock::TestCase::TestInteraction
|
|
17
|
+
def initialize
|
|
18
|
+
@client_socket, @server_socket = UNIXSocket.pair
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def handshake
|
|
22
|
+
@handshake ||= WebSocket::Handshake::Client.new(:url => 'ws://example.com')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def version
|
|
26
|
+
handshake.version
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def env
|
|
30
|
+
request = WEBrick::HTTPRequest.new( :ServerSoftware => "" )
|
|
31
|
+
request.parse(StringIO.new(handshake.to_s))
|
|
32
|
+
rest = handshake.to_s.slice((request.to_s.length..-1))
|
|
33
|
+
|
|
34
|
+
request.meta_vars.merge(
|
|
35
|
+
"rack.input" => StringIO.new(rest),
|
|
36
|
+
"rack.hijack" => proc {},
|
|
37
|
+
"rack.hijack_io" => @server_socket
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def write_raw(message)
|
|
42
|
+
@client_socket << message
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def write(message)
|
|
46
|
+
write_raw WebSocket::Frame::Outgoing::Client.new(
|
|
47
|
+
version: version,
|
|
48
|
+
data: JSON.dump(message),
|
|
49
|
+
type: :text
|
|
50
|
+
).to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def read
|
|
54
|
+
framebuffer = WebSocket::Frame::Incoming::Client.new(version: version)
|
|
55
|
+
data, addrinfo = @client_socket.recvfrom(2000)
|
|
56
|
+
framebuffer << data
|
|
57
|
+
JSON.load(framebuffer.next.data)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def join
|
|
61
|
+
@thread.join
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def close
|
|
65
|
+
@client_socket.close
|
|
66
|
+
join
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tubesock
|
|
70
|
+
tubesock = Tubesock.hijack(env)
|
|
71
|
+
yield tubesock
|
|
72
|
+
@thread = tubesock.listen
|
|
73
|
+
@client_socket.recvfrom 2000 # flush the handshake
|
|
74
|
+
tubesock
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class MockProc
|
|
79
|
+
attr_reader :called
|
|
80
|
+
def to_proc
|
|
81
|
+
proc { @called = true }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
# Force autoloading of classes
|
|
4
|
+
WebSocket::Frame::Incoming::Client
|
|
5
|
+
WebSocket::Frame::Outgoing::Client
|
|
6
|
+
WebSocket::Frame::Incoming::Server
|
|
7
|
+
WebSocket::Frame::Outgoing::Server
|
|
8
|
+
|
|
9
|
+
class TubesockTest < Tubesock::TestCase
|
|
10
|
+
|
|
11
|
+
def test_raise_exception_when_hijack_is_not_available
|
|
12
|
+
-> {
|
|
13
|
+
Tubesock.hijack({})
|
|
14
|
+
}.must_raise Tubesock::HijackNotAvailable
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_hijack
|
|
18
|
+
interaction = TestInteraction.new
|
|
19
|
+
|
|
20
|
+
opened = MockProc.new
|
|
21
|
+
closed = MockProc.new
|
|
22
|
+
|
|
23
|
+
interaction.tubesock do |tubesock|
|
|
24
|
+
tubesock.onopen &opened
|
|
25
|
+
tubesock.onclose &closed
|
|
26
|
+
|
|
27
|
+
tubesock.onmessage do |message|
|
|
28
|
+
tubesock.send_data message: "Hello #{message["name"]}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
interaction.write name: "Nick"
|
|
33
|
+
data = interaction.read
|
|
34
|
+
data["message"].must_equal "Hello Nick"
|
|
35
|
+
interaction.close
|
|
36
|
+
|
|
37
|
+
opened.called.must_equal true
|
|
38
|
+
closed.called.must_equal true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_close_on_close_frame
|
|
42
|
+
interaction = TestInteraction.new
|
|
43
|
+
|
|
44
|
+
closed = MockProc.new
|
|
45
|
+
|
|
46
|
+
interaction.tubesock do |tubesock|
|
|
47
|
+
tubesock.onclose &closed
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# That's what Firefox sends when disconnecting.
|
|
51
|
+
interaction.write_raw "\x88\x82\xA3\x95\xD5\xB1\xA0|".force_encoding('binary')
|
|
52
|
+
|
|
53
|
+
interaction.join
|
|
54
|
+
|
|
55
|
+
closed.called.must_equal true
|
|
56
|
+
end
|
|
57
|
+
end
|
data/tubesock.gemspec
CHANGED
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.email = ["ngauthier@gmail.com"]
|
|
11
11
|
spec.description = %q{Websocket interface on Rack Hijack w/ Rails support}
|
|
12
12
|
spec.summary = %q{Handle websocket connections via Rack and Rails 4 using concurrency}
|
|
13
|
-
spec.homepage = ""
|
|
13
|
+
spec.homepage = "http://github.com/ngauthier/tubesock"
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
|
@@ -23,4 +23,7 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
|
25
25
|
spec.add_development_dependency "rake"
|
|
26
|
+
spec.add_development_dependency "minitest", "~> 4.7.3"
|
|
27
|
+
spec.add_development_dependency "simplecov"
|
|
28
|
+
spec.add_development_dependency "activesupport"
|
|
26
29
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tubesock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Gauthier
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2013-
|
|
11
|
+
date: 2013-06-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -66,6 +66,48 @@ dependencies:
|
|
|
66
66
|
- - '>='
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: minitest
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ~>
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 4.7.3
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ~>
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 4.7.3
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: simplecov
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - '>='
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - '>='
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: activesupport
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - '>='
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - '>='
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
69
111
|
description: Websocket interface on Rack Hijack w/ Rails support
|
|
70
112
|
email:
|
|
71
113
|
- ngauthier@gmail.com
|
|
@@ -74,6 +116,7 @@ extensions: []
|
|
|
74
116
|
extra_rdoc_files: []
|
|
75
117
|
files:
|
|
76
118
|
- .gitignore
|
|
119
|
+
- .travis.yml
|
|
77
120
|
- Gemfile
|
|
78
121
|
- LICENSE.txt
|
|
79
122
|
- README.md
|
|
@@ -81,8 +124,10 @@ files:
|
|
|
81
124
|
- lib/tubesock.rb
|
|
82
125
|
- lib/tubesock/hijack.rb
|
|
83
126
|
- lib/tubesock/version.rb
|
|
127
|
+
- test/test_helper.rb
|
|
128
|
+
- test/tubesock_test.rb
|
|
84
129
|
- tubesock.gemspec
|
|
85
|
-
homepage:
|
|
130
|
+
homepage: http://github.com/ngauthier/tubesock
|
|
86
131
|
licenses:
|
|
87
132
|
- MIT
|
|
88
133
|
metadata: {}
|
|
@@ -106,4 +151,6 @@ rubygems_version: 2.0.3
|
|
|
106
151
|
signing_key:
|
|
107
152
|
specification_version: 4
|
|
108
153
|
summary: Handle websocket connections via Rack and Rails 4 using concurrency
|
|
109
|
-
test_files:
|
|
154
|
+
test_files:
|
|
155
|
+
- test/test_helper.rb
|
|
156
|
+
- test/tubesock_test.rb
|