em-ws-client 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,11 @@
1
+ $:.unshift File.dirname(__FILE__) + "/lib"
2
+
3
+ require "em-ws-client"
4
+
1
5
  spec = Gem::Specification.new do |s|
2
6
  s.name = "em-ws-client"
3
- s.version = "0.1.2"
4
- s.date = "2011-09-26"
7
+ s.version = EM::WebSocketClient::Version
8
+ s.date = "2012-04-14"
5
9
  s.summary = "EventMachine WebSocket Client"
6
10
  s.email = "dan@shove.io"
7
11
  s.homepage = "https://github.com/dansimpson/em-ws-client"
@@ -9,7 +13,6 @@ spec = Gem::Specification.new do |s|
9
13
  s.has_rdoc = true
10
14
 
11
15
  s.add_dependency("eventmachine", "~> 1.0.0.beta.4")
12
- s.add_dependency("state_machine", "~> 1.0.2")
13
16
 
14
17
  s.authors = ["Dan Simpson"]
15
18
 
@@ -1,196 +1,16 @@
1
1
  require "rubygems"
2
2
  require "eventmachine"
3
- require "state_machine"
4
3
  require "uri"
5
- require "digest/sha1"
6
4
  require "base64"
7
- require "codec/draft10encoder.rb"
8
- require "codec/draft10decoder.rb"
9
-
10
-
11
- module EM
12
- class WebSocketClient
13
-
14
- Version = "0.1.2"
15
-
16
- class WebSocketConnection < EM::Connection
17
-
18
- def client=(client)
19
- @client = client
20
- @client.connection = self
21
- end
22
-
23
- def receive_data(data)
24
- @client.receive_data data
25
- end
26
-
27
- def unbind(reason=nil)
28
- @client.disconnect
29
- end
30
-
31
- end
32
-
33
- attr_accessor :connection
34
-
35
- state_machine :initial => :disconnected do
36
-
37
- # States
38
- state :disconnected
39
- state :connecting
40
- state :negotiating
41
- state :established
42
- state :failed
43
-
44
- after_transition :to => :connecting, :do => :connect
45
- after_transition :to => :negotiating, :do => :on_negotiating
46
- after_transition :to => :established, :do => :on_established
47
-
48
- event :start do
49
- transition :disconnected => :connecting
50
- end
51
-
52
- event :negotiate do
53
- transition :connecting => :negotiating
54
- end
55
-
56
- event :complete do
57
- transition :negotiating => :established
58
- end
59
-
60
- event :error do
61
- transition all => :failed
62
- end
63
-
64
- event :disconnect do
65
- transition all => :disconnected
66
- end
67
-
68
- end
69
-
70
- def initialize uri, origin="em-websocket-client"
71
- super();
72
-
73
- @uri = URI.parse(uri)
74
- @origin = origin
75
- @queue = []
76
-
77
- @encoder = Draft10Encoder.new
78
- @decoder = Draft10Decoder.new
79
-
80
- @request_key = build_request_key
81
- @buffer = ""
82
-
83
- start
84
- end
85
-
86
- # Called on opening of the websocket
87
- def onopen &block
88
- @open_handler = block
89
- end
90
-
91
- # Called on the close of the connection
92
- def onclose &block
93
- @cblock = block
94
- end
95
-
96
- # Called when a message is received
97
- def onmessage &block
98
- @message_handler = block
99
- end
100
-
101
- # EM callback
102
- def receive_data(data)
103
- if negotiating?
104
- @buffer << data
105
- request, rest = @buffer.split("\r\n\r\n", 2)
106
- if rest
107
- @buffer = ""
108
- handle_response(request)
109
- receive_data rest
110
- end
111
- else
112
- message = @decoder.decode(data)
113
- if message
114
- if @message_handler
115
- @message_handler.call(message)
116
- end
117
- end
118
- end
119
- end
120
-
121
- # Send a WebSocket frame to the remote
122
- # host.
123
- def send_data data
124
- if established?
125
- connection.send_data(@encoder.encode(data))
126
- else
127
- @queue << data
128
- end
129
- end
130
-
131
- private
132
-
133
- # Connect to the remote host and synchonize the connection
134
- # and this client object
135
- def connect
136
- EM.connect @uri.host, @uri.port || 80, WebSocketConnection do |conn|
137
- conn.client = self
138
- negotiate
139
- end
140
- end
141
-
142
- # Send HTTP request with upgrade goodies
143
- # to the remote host
144
- def on_negotiating
145
- request = "GET #{@uri.path} HTTP/1.1\r\n"
146
- request << "Upgrade: WebSocket\r\n"
147
- request << "Connection: Upgrade\r\n"
148
- request << "Host: #{@uri.host}\r\n"
149
- request << "Sec-WebSocket-Key: #{@request_key}\r\n"
150
- request << "Sec-WebSocket-Version: 8\r\n"
151
- request << "Sec-WebSocket-Origin: #{@origin}\r\n"
152
- request << "\r\n"
153
- connection.send_data(request)
154
- end
155
-
156
- def on_established
157
- if @open_handler
158
- @open_handler.call
159
- end
160
-
161
- while !@queue.empty?
162
- send_data @queue.shift
163
- end
164
- end
165
-
166
- # Handle the HTTP response and ensure it's valid
167
- # by checking the Sec-WebSocket-Accept header
168
- def handle_response response
169
- lines = response.split("\r\n")
170
- table = {}
171
-
172
- lines.each do |line|
173
- header = /^([^:]+):\s*(.+)$/.match(line)
174
- table[header[1].downcase.strip] = header[2].strip if header
175
- end
176
-
177
- if table["sec-websocket-accept"] == build_response_key
178
- complete
179
- else
180
- error
181
- end
182
- end
183
-
184
- # Build a unique request key to match against
185
- def build_request_key
186
- Base64.encode64(Time.now.to_i.to_s(16)).chomp
187
- end
5
+ require "digest/sha1"
188
6
 
189
- # Build the response key from the given request key
190
- # for comparison with the response value.
191
- def build_response_key
192
- Base64.encode64(Digest::SHA1.digest("#{@request_key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11")).chomp
193
- end
194
-
7
+ module EventMachine
8
+ module WebSocketCodec
195
9
  end
196
- end
10
+ end
11
+
12
+ require "em-ws-client/handshake.rb"
13
+ require "em-ws-client/protocol.rb"
14
+ require "em-ws-client/encoder.rb"
15
+ require "em-ws-client/decoder.rb"
16
+ require "em-ws-client/client.rb"
@@ -0,0 +1,300 @@
1
+ # encoding: UTF-8
2
+
3
+ module EventMachine
4
+
5
+ # Public: A fully functional WebSocket client
6
+ # implementation.
7
+ #
8
+ # Examples
9
+ #
10
+ # ws = WebSocketClient.new "ws://localhost/chat"
11
+ #
12
+ # ws.onmessage do |msg|
13
+ # puts msg
14
+ # end
15
+ #
16
+ # ws.onopen do
17
+ # ws.send_message "Hello!"
18
+ # end
19
+ class WebSocketClient
20
+
21
+ Version = "0.2.0"
22
+
23
+ class WebSocketError < StandardError; end
24
+
25
+ # Internal: Wrapper
26
+ class WebSocketConnection < EM::Connection
27
+
28
+ def client=(client)
29
+ @client = client
30
+ @client.socket = self
31
+ end
32
+
33
+ def receive_data(data)
34
+ @client.receive_data data
35
+ end
36
+
37
+ def unbind(reason=nil)
38
+ @client.unbind
39
+ end
40
+
41
+ end
42
+
43
+ include WebSocketCodec::Protocol
44
+
45
+ attr_accessor :socket
46
+
47
+ # Public: Initialize a WebSocket client
48
+ #
49
+ # uri - The endpoint host url
50
+ # origin - The origin you wish to claim
51
+ #
52
+ # Examples
53
+ #
54
+ # WebSocketClient.new "ws://localhost:9000/chat"
55
+ # WebSocketClient.new "ws://ws.site.com/chat", "http://www.site.com/chat"
56
+ #
57
+ # Returns the client
58
+ def initialize uri, origin="em-ws-client"
59
+ super();
60
+
61
+ @uri = URI.parse(uri)
62
+ @origin = origin
63
+ @buffer = ""
64
+
65
+ @encoder = WebSocketCodec::Encoder.new
66
+ @decoder = WebSocketCodec::Decoder.new
67
+ @handshake = WebSocketCodec::Handshake.new @uri, @origin
68
+
69
+ @callbacks = {}
70
+ @closing = false
71
+
72
+ connect
73
+ end
74
+
75
+ # Public: Close the connection
76
+ #
77
+ # Examples
78
+ #
79
+ # ws.unbind
80
+ # # => ?
81
+ #
82
+ # Returns
83
+ def unbind
84
+ emit :close
85
+ end
86
+
87
+ # Bind a callback to the open event
88
+ #
89
+ # block - A block which is called when
90
+ # the connection to the remote host is established
91
+ #
92
+ # Examples
93
+ #
94
+ # ws.onopen do
95
+ # end
96
+ #
97
+ # Returns nothing
98
+ def onopen &block
99
+ @callbacks[:open] = block
100
+ end
101
+
102
+ # Bind a callback to the close event
103
+ #
104
+ # block - A block which is called when
105
+ # the connection to the remote host is closed.
106
+ # Your block receives 2 arguments, with the second
107
+ # potentially being nil.
108
+ #
109
+ # Examples
110
+ #
111
+ # ws.onclose do |code, explain|
112
+ # end
113
+ #
114
+ # Returns nothing
115
+ def onclose &block
116
+ @callbacks[:close] = block
117
+ end
118
+
119
+ # Bind a callback to the message event
120
+ #
121
+ # block - A block which is called when a
122
+ # message is received. The first argument
123
+ # for the block is the message, and the second
124
+ # argument is a binary flag.
125
+ #
126
+ # Examples
127
+ #
128
+ # ws.onmessage do |message, binary|
129
+ # end
130
+ #
131
+ # Returns nothing
132
+ def onmessage &block
133
+ @callbacks[:frame] = block
134
+ end
135
+
136
+ # Bind a callback to the error event
137
+ #
138
+ # block - A block which is called when
139
+ # an error occurs. The connection is dropped
140
+ # immediately per spec. The first argument is
141
+ # the close code, and the second is the error.
142
+ #
143
+ # Examples
144
+ #
145
+ # ws.onerror do |close_code, error|
146
+ # end
147
+ #
148
+ # Returns nothing
149
+ def onerror &block
150
+ @callbacks[:error] = block
151
+ end
152
+
153
+ # Bind a callback to the ping event
154
+ #
155
+ # block - A block which is called when
156
+ # the remote host sends a ping. A single
157
+ # argument is sent, which contains the ping
158
+ # data sent from the remote host. A pong
159
+ # is automatically sent.
160
+ #
161
+ # Examples
162
+ #
163
+ # ws.onping do |data|
164
+ # end
165
+ #
166
+ # Returns nothing
167
+ def onping &block
168
+ @callbacks[:ping] = block
169
+ end
170
+
171
+ # Bind a callback to the pong event
172
+ #
173
+ # block - A block which is called when
174
+ # the remote host sends a pong in response
175
+ # to your ping. It's possible to get unwarrented
176
+ # pongs.
177
+ #
178
+ # Examples
179
+ #
180
+ # ws.onpong do |data|
181
+ # end
182
+ #
183
+ # Returns nothing
184
+ def onpong &block
185
+ @callbacks[:pong] = block
186
+ end
187
+
188
+ # Internal: called by eventmachine when data is
189
+ # received
190
+ def receive_data(data)
191
+ if @handshake.complete?
192
+ receive_message_data data
193
+ else
194
+ receive_handshake_data data
195
+ end
196
+ end
197
+
198
+
199
+ # Send a message to the remote host
200
+ #
201
+ # data - The string contents of your message
202
+ #
203
+ # Examples
204
+ #
205
+ # ws.onping do |data|
206
+ # end
207
+ #
208
+ # Returns nothing
209
+ def send_message data, binary=false
210
+ if established?
211
+ unless @closing
212
+ @socket.send_data(@encoder.encode(data.to_s, binary ? BINARY_FRAME : TEXT_FRAME))
213
+ end
214
+ else
215
+ raise WebSocketError.new "can't send on a closed channel"
216
+ end
217
+ end
218
+
219
+ def close code=1000, msg=nil
220
+ @closing = true
221
+ @socket.send_data @encoder.close(code, msg)
222
+ @socket.close_connection_after_writing
223
+ end
224
+
225
+ private
226
+
227
+ # Internal: is the handshake complete and valid?
228
+ def established?
229
+ @handshake.complete? && @handshake.valid?
230
+ end
231
+
232
+ # Internal: process ws data
233
+ def receive_message_data data
234
+ @decoder << data
235
+ end
236
+
237
+ # Internal: process handshake data
238
+ def receive_handshake_data data
239
+ @handshake << data
240
+ if @handshake.complete?
241
+ if @handshake.valid?
242
+ on_handshake_complete
243
+ else
244
+ emit :error, 1, "Handshake failed!"
245
+ @socket.unbind
246
+ end
247
+ end
248
+ end
249
+
250
+ # Internal: setup encoder/decoder and bind
251
+ # to all decoder events.
252
+ def on_handshake_complete
253
+
254
+ @decoder.onping do |data|
255
+ @socket.send_data @encoder.pong(data)
256
+ emit :ping, data
257
+ end
258
+
259
+ @decoder.onpong do |data|
260
+ emit :pong, data
261
+ end
262
+
263
+ @decoder.onclose do |code|
264
+ close code
265
+ end
266
+
267
+ @decoder.onframe do |frame, binary|
268
+ emit :frame, frame, binary
269
+ end
270
+
271
+ @decoder.onerror do |code, message|
272
+ close code, message
273
+ emit :error, code, message
274
+ end
275
+
276
+ emit :open
277
+
278
+ if @handshake.extra
279
+ receive_message_data @handshake.extra
280
+ end
281
+ end
282
+
283
+ # Internal: Connect to the remote host and synchonize the socket
284
+ # and this client object
285
+ def connect
286
+ EM.connect @uri.host, @uri.port || 80, WebSocketConnection do |conn|
287
+ conn.client = self
288
+ conn.send_data(@handshake.request)
289
+ end
290
+ end
291
+
292
+ # Internal: Emit an event
293
+ def emit event, *args
294
+ if @callbacks.key?(event)
295
+ @callbacks[event].call(*args)
296
+ end
297
+ end
298
+
299
+ end
300
+ end