tubesock 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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