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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e34c2f3cd416319f09063d1420a92bb4de54f41
4
- data.tar.gz: 35232dc37e37a9abd7b2595d4eeb8b9e9b252b39
3
+ metadata.gz: 0a5fc91c6e49797a1595e75ac1fa8425259370a3
4
+ data.tar.gz: 3af86ddcbe0fe0cf80aac51d25f27420ca2bb444
5
5
  SHA512:
6
- metadata.gz: 81501d0966fd116753dfe71231626c3cda459672fef63bde5f12a0df305ed91d89fafba1179a29ea825562aa6052bf40c94d0e72c568e46b3bdba2d5daa417f2
7
- data.tar.gz: 5a06e190adfd2967405e22af868f0967d671b20613c30682fa4bf3d925579e9f49df83d582c4729f640c61cdde314e86d86f309f726d0891e4370f3f36cbab87
6
+ metadata.gz: b6fa82ff09d335de5942b2ee415a328c15a40ba579f82f1f1293dd6c40de9edb100eb633384d1debb7cde05c017ff69239e9ceff8da37bf8f8c583a9338757aa
7
+ data.tar.gz: 3b5050954b14fafc27df74ddaabb5a34f3e8399db95e3d256f5b71c14385f803a28a552a026fd679c2c3ec5e43408b9b0c3881806343e1dd2cc5129edf9d3f1f
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+ - rbx-19mode
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in tubesock.gemspec
4
+ gem "websocket", github: "imanel/websocket-ruby", ref: "49cc8e00eca912726c9980ccb6c927afd718c2de"
4
5
  gemspec
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Tubesock
2
2
 
3
+ [![Build Status](https://travis-ci.org/ngauthier/tubesock.png)](https://travis-ci.org/ngauthier/tubesock) [![Code Climate](https://codeclimate.com/github/ngauthier/tubesock.png)](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", ref: "8fc3bbc8f336fb5ccac95b8707e8146e86a8002d"
17
+ gem 'websocket', github: "ngauthier/websocket-ruby"
16
18
  ```
17
19
 
18
20
  And then execute:
data/Rakefile CHANGED
@@ -1 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList["test/**/*_test.rb"]
7
+ t.verbose = true
8
+ end
9
+
10
+ task default: :test
data/lib/tubesock.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require "tubesock/version"
2
- require "tubesock/hijack" if defined?(Rails)
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(version: @version, data: JSON.dump(data), type: type)
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
- while running
66
- data, addrinfo = @socket.recvfrom(2000)
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
@@ -1,25 +1,39 @@
1
1
  require 'active_support/concern'
2
- class Tubesock
3
- module Hijack
4
- extend ActiveSupport::Concern
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
- included do
7
- def hijack
8
- if Tubesock.websocket?(env)
9
- sock = Tubesock.hijack(env)
10
- yield sock
11
- sock.onclose do
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
- end
21
-
22
- module ClassMethods
35
+ sock.listen
36
+ render text: nil, status: -1
23
37
  end
24
38
  end
25
39
  end
@@ -1,3 +1,3 @@
1
1
  class Tubesock
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -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.2
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-03-21 00:00:00.000000000 Z
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