websocket-td 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +17 -0
- data/VERSION +1 -0
- data/examples/demo.rb +27 -0
- data/lib/errors.rb +8 -0
- data/lib/websocket_td.rb +192 -0
- data/websocket_td.gemspec +23 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 549ad5df2619ccc64b9be924d85d1bb34e7d5601
|
4
|
+
data.tar.gz: 8e3a9c62a78aab4095f71e8fa26c0cf922127ee3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d64310c37daa3d8783c82249d76b5933e5d31c191c9b2fb837f0d49edd59a684dd5e87abe8abc0281d23459cb203f1299ae258addace1c8df3775b7dc0d00dbb
|
7
|
+
data.tar.gz: b17d9279fa42be544bad826b647c49ac0dd269db0379806aa53b32e42decb045b417ca9c7fcb434998951af81540e3fb1cf7834cc6f4bc6eb5c0df479e4a7d97
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2011-2012 MediaSift Ltd
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
WebSocket TD
|
2
|
+
========
|
3
|
+
|
4
|
+
A standards compliant WebSocket client which offers the option of either using 'green' threads or a single threaded evented approach
|
5
|
+
Both ws and wss protocols supported.
|
6
|
+
|
7
|
+
Install Instructions
|
8
|
+
--------------------
|
9
|
+
|
10
|
+
sudo gem install websocket-td
|
11
|
+
|
12
|
+
Dependencies
|
13
|
+
------------
|
14
|
+
|
15
|
+
|
16
|
+
gem install websocket, multi_json, rdoc, shoulda, test-unit
|
17
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
data/examples/demo.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Demo
|
2
|
+
require '../lib/websocket_td'
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
|
6
|
+
client = WebsocketTD::Websocket.new('websocket.datasift.com', '/multi', 'username=zcourts&api_key=91207449dd5b89a4ff38879a770975bf')
|
7
|
+
puts 'I continued'
|
8
|
+
|
9
|
+
client.on_message = lambda { |message| puts "message: #{message.data}" }
|
10
|
+
|
11
|
+
client.on_open = lambda {
|
12
|
+
puts 'opened'
|
13
|
+
client.send("{ \"action\":\"subscribe\",\"hash\":\"1f678ba99fbcad0b572011b390cf5124\"}")
|
14
|
+
}
|
15
|
+
client.on_close = lambda { |m| puts "Closed #{m}" }
|
16
|
+
while true
|
17
|
+
puts 'subscribing'
|
18
|
+
if client.active
|
19
|
+
client.send("{ \"action\":\"subscribe\",\"hash\":\"1f678ba99fbcad0b572011b390cf5124\"}")
|
20
|
+
end
|
21
|
+
sleep(1)
|
22
|
+
end
|
23
|
+
#if main thread finishes then the process exists...don't let that happen
|
24
|
+
client.read_thread.join
|
25
|
+
end
|
26
|
+
end
|
27
|
+
Demo.new
|
data/lib/errors.rb
ADDED
data/lib/websocket_td.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'websocket'
|
2
|
+
require 'socket'
|
3
|
+
require '../lib/errors'
|
4
|
+
|
5
|
+
module WebsocketTD
|
6
|
+
class Websocket
|
7
|
+
# max length bytes to try to read from a socket per attempt
|
8
|
+
@read_buffer = 0
|
9
|
+
#true when reading data from a socket
|
10
|
+
@active = false
|
11
|
+
#the tread currently being used to read data
|
12
|
+
@read_thread = nil
|
13
|
+
@auto_pong = true
|
14
|
+
##
|
15
|
+
# +host+:: Host of request. Required if no :url param was provided.
|
16
|
+
# +path+:: Path of request. Should start with '/'. Default: '/'
|
17
|
+
# +query+:: Query for request. Should be in format "aaa=bbb&ccc=ddd"
|
18
|
+
# +secure+:: Defines protocol to use. If true then wss://, otherwise ws://. This option will not change default port - it should be handled by programmer.
|
19
|
+
# +port+:: Port of request. Default: nil
|
20
|
+
def initialize(host, path, query, secure = false, port = nil)
|
21
|
+
if port == nil
|
22
|
+
port = secure ? 443 : 80
|
23
|
+
end
|
24
|
+
|
25
|
+
@handshake = WebSocket::Handshake::Client.new({
|
26
|
+
:host => host,
|
27
|
+
:port => port,
|
28
|
+
:secure => secure,
|
29
|
+
:path => path,
|
30
|
+
:query => query
|
31
|
+
})
|
32
|
+
|
33
|
+
@read_buffer = 2048
|
34
|
+
@auto_pong = true
|
35
|
+
@closed = false
|
36
|
+
@opened = false
|
37
|
+
|
38
|
+
@on_open = lambda {}
|
39
|
+
@on_close = lambda { |message|}
|
40
|
+
@on_ping = lambda { |message|}
|
41
|
+
@on_error = lambda { |error|}
|
42
|
+
@on_message = lambda { |message|}
|
43
|
+
|
44
|
+
tcp_socket = TCPSocket.new(host, port)
|
45
|
+
if secure
|
46
|
+
@socket = OpenSSL::SSL::SSLSocket.new(tcp_socket)
|
47
|
+
@socket.connect
|
48
|
+
else
|
49
|
+
@socket = tcp_socket
|
50
|
+
end
|
51
|
+
|
52
|
+
@socket.write @handshake.to_s
|
53
|
+
buf = ''
|
54
|
+
headers = ''
|
55
|
+
reading = true
|
56
|
+
|
57
|
+
while reading
|
58
|
+
begin
|
59
|
+
if @handshake.finished?
|
60
|
+
init_messaging
|
61
|
+
#don't stop reading until after init_message to guarantee @read_thread != nil for a successful connection
|
62
|
+
reading = false
|
63
|
+
@opened =true
|
64
|
+
fire_on_open
|
65
|
+
else
|
66
|
+
#do non blocking reads on headers - 1 byte at a time
|
67
|
+
buf.concat(@socket.read_nonblock(1))
|
68
|
+
#\r\n\r\n i.e. a blank line, separates headers from body
|
69
|
+
idx = buf.index(/\r\n\r\n/m)
|
70
|
+
if idx != nil
|
71
|
+
headers = buf.slice!(0..idx + 8) #+8 to include the blank line separator
|
72
|
+
@handshake << headers #parse headers
|
73
|
+
|
74
|
+
if @handshake.finished? && !@handshake.valid?
|
75
|
+
fire_on_error(ConnectError.new('Server responded with an invalid handshake'))
|
76
|
+
fire_on_close() #close if handshake is not valid
|
77
|
+
end
|
78
|
+
|
79
|
+
if @handshake.finished?
|
80
|
+
@active = true
|
81
|
+
buf = '' #clean up
|
82
|
+
headers = ''
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
rescue IO::WaitReadable
|
87
|
+
# ignored
|
88
|
+
rescue IO::WaitWritable
|
89
|
+
# ignored
|
90
|
+
end
|
91
|
+
end
|
92
|
+
puts headers
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_reader :read_thread, :read_buffer, :socket, :active, :auto_pong
|
96
|
+
attr_writer :read_buffer, :auto_pong, :on_ping, :on_error, :on_message # :on_open, :on_close
|
97
|
+
|
98
|
+
def on_open=(p)
|
99
|
+
@on_open = p
|
100
|
+
if @opened
|
101
|
+
fire_on_open
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_close=(p)
|
106
|
+
@on_close = p
|
107
|
+
if @closed
|
108
|
+
fire_on_close
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#Use one thread to perform blocking read on the socket
|
113
|
+
def init_messaging
|
114
|
+
if @read_thread == nil
|
115
|
+
@read_thread = Thread.new do
|
116
|
+
read_loop
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_loop
|
122
|
+
frame = WebSocket::Frame::Incoming::Client.new(:version => @handshake.version)
|
123
|
+
while @active
|
124
|
+
if @socket.closed?
|
125
|
+
@active = false
|
126
|
+
fire_on_close
|
127
|
+
else
|
128
|
+
frame << @socket.readpartial(@read_buffer)
|
129
|
+
if (message = frame.next) != nil
|
130
|
+
#"text", "binary", "ping", "pong" and "close" (according to websocket/base.rb)
|
131
|
+
determine_message_type(message)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def determine_message_type(message)
|
138
|
+
if message.type == :binary || message.type == :text
|
139
|
+
fire_on_message(message)
|
140
|
+
elsif message.type == :ping
|
141
|
+
if @auto_pong
|
142
|
+
send(message.data, :pong)
|
143
|
+
end
|
144
|
+
fire_on_ping(message)
|
145
|
+
elsif message.type == :pong
|
146
|
+
fire_on_error(WsProtocolError.new('Invalid type pong received'))
|
147
|
+
elsif message.type == :close
|
148
|
+
fire_on_close(message)
|
149
|
+
else
|
150
|
+
fire_on_error(BadMessageTypeError.new("An unknown message type was received #{message.data}"))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
def send(data, type = :text)
|
156
|
+
frame = WebSocket::Frame::Outgoing::Client.new(:version => @handshake.version, :data => data, :type => type)
|
157
|
+
begin
|
158
|
+
@socket.write frame #_nonblock
|
159
|
+
@socket.flush
|
160
|
+
rescue Errno::EPIPE => ce
|
161
|
+
fire_on_error(ce)
|
162
|
+
fire_on_close
|
163
|
+
rescue Exception => e
|
164
|
+
fire_on_error(e)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def fire_on_ping(message)
|
169
|
+
@on_ping.call(message) unless @on_ping == nil
|
170
|
+
end
|
171
|
+
|
172
|
+
def fire_on_message(message)
|
173
|
+
@on_message.call(message) unless @on_message == nil
|
174
|
+
end
|
175
|
+
|
176
|
+
def fire_on_open
|
177
|
+
@on_open.call() unless @on_open == nil
|
178
|
+
end
|
179
|
+
|
180
|
+
def fire_on_error(error)
|
181
|
+
@on_error.call(error) unless @on_error == nil
|
182
|
+
end
|
183
|
+
|
184
|
+
def fire_on_close(message = nil)
|
185
|
+
@active = false
|
186
|
+
@closed = true
|
187
|
+
@on_close.call(message) unless @on_close == nil
|
188
|
+
@socket.close
|
189
|
+
end
|
190
|
+
|
191
|
+
end # class
|
192
|
+
end # module
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'websocket-td'
|
3
|
+
s.version = File.open('VERSION').first
|
4
|
+
s.authors = ['Courtney Robinson']
|
5
|
+
s.email = ['courtney.robinson@datasift.com']
|
6
|
+
s.description = %q{A multi-threaded WebSocket client, i.e. without event machine}
|
7
|
+
s.summary = %q{A client which will offer the option of using green threads or a single threaded evented approach}
|
8
|
+
s.homepage = 'http://github.com/zcourts/websocket-td'
|
9
|
+
s.license = 'BSD'
|
10
|
+
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.rubygems_version = %q{1.3.6}
|
13
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.3.6") if s.respond_to? :required_rubygems_version=
|
14
|
+
|
15
|
+
s.add_runtime_dependency('websocket', '~> 1.1.1')
|
16
|
+
s.add_development_dependency('rdoc', '> 0')
|
17
|
+
s.add_development_dependency('shoulda', '~> 2.11.3')
|
18
|
+
s.add_development_dependency('test-unit', '>= 2.5.5')
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: websocket-td
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Courtney Robinson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: websocket
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>'
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>'
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: shoulda
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.11.3
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.11.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: test-unit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.5.5
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.5.5
|
69
|
+
description: A multi-threaded WebSocket client, i.e. without event machine
|
70
|
+
email:
|
71
|
+
- courtney.robinson@datasift.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE
|
79
|
+
- README.md
|
80
|
+
- VERSION
|
81
|
+
- examples/demo.rb
|
82
|
+
- lib/errors.rb
|
83
|
+
- lib/websocket_td.rb
|
84
|
+
- websocket_td.gemspec
|
85
|
+
homepage: http://github.com/zcourts/websocket-td
|
86
|
+
licenses:
|
87
|
+
- BSD
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.3.6
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.1.5
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A client which will offer the option of using green threads or a single threaded
|
109
|
+
evented approach
|
110
|
+
test_files: []
|