websocket-client 0.1.9 → 0.2.0

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.
@@ -5,22 +5,21 @@ $: << File.dirname(__FILE__) + '/../lib'
5
5
  require 'websocket_client'
6
6
 
7
7
  ## Create a disconnected client
8
- client = WebSocketClient.create( 'ws://localhost:8081/websockets/' )
8
+ client = WebSocketClient.create( 'ws://localhost:8081/websockets/echo/' )
9
9
 
10
- ## Set up the message handler before connecting
10
+ ## Set up the message handler before connecting
11
11
  client.on_message do |msg|
12
- puts "received #{msg}"
12
+ puts "> received message: #{msg}"
13
13
  end
14
14
 
15
15
  ## Connect
16
16
  client.connect()
17
17
 
18
18
  ## Use the connected client
19
- puts "sending"
20
19
  client.send( "HOWDY-1" )
21
20
  client.send( "HOWDY-2" )
22
21
  client.send( "HOWDY-3" )
23
22
  sleep(1)
24
23
 
25
- ## Explicit disconncet
26
- client.disconnect
24
+ ## Explicit disconncet and wait
25
+ client.disconnect( true )
@@ -0,0 +1,48 @@
1
+
2
+ module WebSocketClient
3
+
4
+ class ByteSink
5
+
6
+ CR = 0x0D
7
+ NL = 0x0A
8
+
9
+ def write_line(line)
10
+ line.bytes.each do |b|
11
+ write( b )
12
+ end
13
+ write( CR )
14
+ write( NL )
15
+ end
16
+
17
+ def flush
18
+ end
19
+ end
20
+
21
+ class SocketByteSink < ByteSink
22
+ def initialize(socket)
23
+ @socket = socket
24
+ end
25
+
26
+ def write(byte)
27
+ @socket.putc( byte )
28
+ end
29
+
30
+ def flush
31
+ @socket.flush
32
+ end
33
+ end
34
+
35
+ class ArrayByteSink < ByteSink
36
+ def initialize()
37
+ @sink = []
38
+ end
39
+
40
+ def write(byte)
41
+ @sink << byte
42
+ end
43
+
44
+ def bytes
45
+ @sink
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,102 @@
1
+
2
+ module WebSocketClient
3
+
4
+ class ByteSource
5
+ def getbytes(num)
6
+ bytes = []
7
+ 1.upto( num ) do
8
+ break if ( eof? )
9
+ bytes << getbyte()
10
+ end
11
+ bytes
12
+ end
13
+ end
14
+
15
+ class BufferedByteSource < ByteSource
16
+ def initialize(source)
17
+ @source = source
18
+ @buffer = []
19
+ end
20
+
21
+ def getbyte
22
+ return @source.getbyte if @buffer.empty?
23
+ @buffer.shift
24
+ end
25
+
26
+ def peekbyte(index=0)
27
+ while ( @buffer.size < (index+1) )
28
+ return nil if ( @source.eof? )
29
+ @buffer << @source.getbyte
30
+ end
31
+ return @buffer[index]
32
+ end
33
+
34
+ def eof?
35
+ return @source.eof? if @buffer.empty?
36
+ false
37
+ end
38
+
39
+ def getline
40
+ line = ''
41
+ while ( ! eof? )
42
+ b = getbyte
43
+ case ( b )
44
+ when 0x0D then # carriage-return
45
+ if peekbyte == 0x0A # newline
46
+ getbyte # consume it also
47
+ end
48
+ break
49
+ when 0x0A then # newline
50
+ break
51
+ else
52
+ line << b
53
+ end
54
+ end
55
+ return line
56
+ end
57
+
58
+
59
+ end
60
+
61
+ class SocketByteSource < ByteSource
62
+ def initialize(socket)
63
+ @socket = socket
64
+ if ( socket.respond_to? :getbyte )
65
+ alias :getbyte :getbyte_19
66
+ else
67
+ alias :getbyte :getbyte_18
68
+ end
69
+ end
70
+
71
+ def getbyte_18
72
+ @socket.getc
73
+ end
74
+
75
+ def getbyte_19
76
+ @socket.getbyte
77
+ end
78
+
79
+ def eof?
80
+ @socket.eof?
81
+ end
82
+
83
+ end
84
+
85
+ class ArrayByteSource < ByteSource
86
+ def initialize(array)
87
+ @array = array
88
+ @cur = 0
89
+ end
90
+
91
+ def getbyte
92
+ b = @array[@cur]
93
+ @cur += 1
94
+ b
95
+ end
96
+
97
+ def eof?
98
+ ( @cur >= @array.size )
99
+ end
100
+
101
+ end
102
+ end
@@ -1,22 +1,12 @@
1
1
  require 'socket'
2
2
  require 'uri'
3
- require 'websocket_client/ietf_00'
3
+ require 'websocket_client/frame_io'
4
+ require 'websocket_client/protocol/ietf_00'
4
5
 
5
6
 
6
7
  module WebSocketClient
7
8
 
8
- DEFAULT_HANDSHAKE = Ietf00
9
-
10
- class DebugSocket
11
- def initialize(socket)
12
- @socket = socket
13
- end
14
-
15
- def method_missing(method, *args)
16
- puts "> #{method}(#{args.join( ', ')})"
17
- @socket.__send__( method, *args )
18
- end
19
- end
9
+ DEFAULT_HANDSHAKE = Protocol::Ietf00
20
10
 
21
11
  def self.create(uri,handshake_class=WebSocketClient::DEFAULT_HANDSHAKE,&block)
22
12
  Client.new(uri, handshake_class, &block)
@@ -25,59 +15,76 @@ module WebSocketClient
25
15
  class Client
26
16
  attr_reader :uri
27
17
 
18
+ attr_reader :source
19
+ attr_reader :sink
20
+
28
21
  def initialize(uri,handshake_class=WebSocketClient::DEFAULT_HANDSHAKE, &block)
29
- @handshake_class = handshake_class
30
- @socket = nil
31
- @on_message_handler = nil
22
+ @handshake_class = handshake_class
23
+ @on_message_handler = nil
32
24
  @on_disconnect_handler = nil
33
- @handler_thread = nil
34
- @close_state = nil
35
- case ( uri )
36
- when String
37
- @uri = URI.parse( uri )
38
- else
39
- @uri = uri
40
- end
25
+ @handler_thread = nil
26
+ @uri = cleanse_uri( uri )
27
+
41
28
  block.call( self ) if block
42
29
  end
30
+
31
+ def cleanse_uri(uri)
32
+ return URI.parse( uri ) if ( String === uri )
33
+ uri
34
+ end
43
35
 
44
36
  def connect(&block)
45
- @socket = TCPSocket.open(uri.host, uri.port)
46
- puts "> handshaking"
47
- @handshake = @handshake_class.new( uri, @socket )
48
- puts "> handshook"
49
-
37
+ socket = TCPSocket.open(uri.host, uri.port)
38
+ @source = BufferedByteSource.new( SocketByteSource.new( socket ) )
39
+ @sink = SocketByteSink.new( socket )
40
+ @handshake = @handshake_class.new( uri, @source, @sink )
41
+
42
+ internal_connect(source, sink)
43
+ run_client( &block )
44
+ end
45
+
46
+ def internal_connect(source, sink)
47
+ @frame_reader = FrameReader.new( source )
48
+ @frame_writer = FrameWriter.new( sink )
49
+ end
50
+
51
+ def run_client(&block)
52
+ on_connect
50
53
  start_handler
51
-
52
54
  if ( block )
53
55
  begin
54
- block.call( self )
56
+ block.call( self )
55
57
  ensure
56
58
  disconnect()
57
59
  end
58
60
  end
59
61
  end
60
62
 
61
- def disconnect
62
- @socket.puts 0xFF
63
- @socket.putc 0x00
64
- @close_state = :requested
63
+ def disconnect(wait_for=false)
64
+ @frame_writer.write_close_frame()
65
+ wait_for_disconnect if wait_for
65
66
  end
66
67
 
67
- def send(msg)
68
- @handshake.encode_text_message( msg )
69
- @socket.flush
68
+ def send(text)
69
+ @frame_writer.write_as_text_frame( text )
70
70
  end
71
71
 
72
- def wait_forever()
72
+ def wait_for_disconnect()
73
73
  @handler_thread.join
74
74
  end
75
75
 
76
+ def on_connect(&block)
77
+ if ( block )
78
+ @on_connect_handler = block
79
+ else
80
+ @on_connect_handler.call() if @on_connect_handler
81
+ end
82
+ end
83
+
76
84
  def on_message(msg=nil,&block)
77
85
  if ( block )
78
86
  @on_message_handler = block
79
87
  else
80
- puts "> on_message #{msg}"
81
88
  @on_message_handler.call( msg ) if @on_message_handler
82
89
  end
83
90
  end
@@ -86,59 +93,27 @@ module WebSocketClient
86
93
  if ( block )
87
94
  @on_disconnect_handler = block
88
95
  else
89
- puts "> on_disconnect"
90
- @on_disconnect_handler.call( msg ) if @on_disconnect_handler
96
+ @on_disconnect_handler.call() if @on_disconnect_handler
91
97
  end
92
98
  end
93
99
 
94
100
  def start_handler
95
- @handler_thread = Thread.new(@socket) do |socket|
96
- msg = ''
97
- msg_state = :none
98
- while ( ! socket.eof? )
99
- c = nil
100
- if ( socket.respond_to?( :getbyte ) )
101
- c = socket.getbyte
102
- else
103
- c = socket.getc
104
- end
105
- puts "> #{c} #{c.class} #{c == 0xff}"
106
- if ( c == 0x00 )
107
- if ( msg_state == :half_closed )
108
- puts "> full-closed by server"
109
- socket.close
110
- break
111
- else
112
- if ( @close_state == :half_closed )
113
- socket.close
114
- break
115
- else
116
- msg_state = :none
117
- end
118
- end
119
- elsif ( c == 0xFF )
120
- if ( msg_state == :none )
121
- msg_state = :half_closed
122
- puts "> half-closed by server"
123
- else
124
- if ( @close_state == :requested )
125
- @close_state = :half_closed
126
- else
127
- on_message( msg )
128
- msg = ''
129
- msg_state = :none
130
- end
131
- end
132
- else
133
- if ( @close_state != nil )
134
- @close_state = nil
135
- end
136
- msg_state = :in_progress
137
- msg << c
138
- end
101
+ @handler_thread = Thread.new() do
102
+ run_handler_loop
103
+ end
104
+ end
105
+
106
+ def run_handler_loop
107
+ while ( ! @frame_reader.eof? )
108
+ frame = @frame_reader.read_frame
109
+ case ( frame.type )
110
+ when :text
111
+ on_message( frame.text )
112
+ when :close
113
+ break
139
114
  end
140
- on_disconnect
141
115
  end
116
+ on_disconnect
142
117
  end
143
118
 
144
119
  end
@@ -0,0 +1,106 @@
1
+
2
+ require 'websocket_client/byte_source'
3
+ require 'websocket_client/byte_sink'
4
+
5
+ module WebSocketClient
6
+
7
+ class TextFrame
8
+
9
+ attr_accessor :text
10
+
11
+ def initialize(text)
12
+ @text = text
13
+ end
14
+
15
+ def type
16
+ :text
17
+ end
18
+
19
+ def bytes
20
+ [ 0x00, @text.bytes.to_a, 0xFF ].flatten
21
+ end
22
+
23
+ end
24
+
25
+ class CloseFrame
26
+ INSTANCE = CloseFrame.new.freeze
27
+
28
+ def type
29
+ :close
30
+ end
31
+
32
+ def bytes
33
+ [ 0xFF, 0x00 ]
34
+ end
35
+
36
+ private
37
+ def initialize()
38
+ end
39
+ end
40
+
41
+ class FrameWriter
42
+ attr_reader :sink
43
+ attr_accessor :debug
44
+
45
+ def initialize(sink,debug=false)
46
+ @sink = sink
47
+ @debug = debug
48
+ end
49
+
50
+ def write_close_frame()
51
+ write_frame( CloseFrame::INSTANCE )
52
+ end
53
+
54
+ def write_as_text_frame(text)
55
+ write_frame( TextFrame.new( text ) )
56
+ end
57
+
58
+ def write_frame(frame)
59
+ frame.bytes.each do |b|
60
+ @sink.write( b )
61
+ end
62
+ @sink.flush
63
+ end
64
+ end
65
+
66
+ class FrameReader
67
+
68
+ attr_reader :source
69
+ attr_accessor :debug
70
+
71
+ def initialize(source,debug=true)
72
+ @source = source
73
+ @debug = debug
74
+ end
75
+
76
+ def eof?
77
+ @source.eof?
78
+ end
79
+
80
+ def read_frame()
81
+ buffer = nil
82
+ state = :none
83
+ while ( ! source.eof? )
84
+ b = source.getbyte
85
+ case ( b )
86
+ when 0x00
87
+ if ( state == :half_closed )
88
+ return CloseFrame::INSTANCE
89
+ end
90
+ buffer = ''
91
+ when 0xFF
92
+ if ( ! buffer.nil? )
93
+ return TextFrame.new( buffer )
94
+ else
95
+ state = :half_closed
96
+ end
97
+ else
98
+ buffer << b
99
+ end
100
+ end
101
+ nil
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,86 @@
1
+ require 'socket'
2
+ require 'uri'
3
+ require 'digest/md5'
4
+
5
+ module WebSocketClient
6
+ module Protocol
7
+ class Ietf00
8
+
9
+ attr_reader :source
10
+ attr_reader :sink
11
+
12
+ def initialize(uri, source, sink)
13
+ @source = source
14
+ @sink = sink
15
+ perform_http_prolog(uri)
16
+ end
17
+
18
+ def perform_http_prolog(uri)
19
+ key1, key2, key3, solution = generate_keys()
20
+
21
+ sink.write_line "GET #{uri.path} HTTP/1.1"
22
+ sink.write_line "Host: #{uri.host}"
23
+ sink.write_line "Connection: upgrade"
24
+ sink.write_line "Upgrade: websocket"
25
+ sink.write_line "Origin: http://#{uri.host}/"
26
+ sink.write_line "Sec-WebSocket-Key1: #{key1}"
27
+ sink.write_line "Sec-WebSocket-Key2: #{key2}"
28
+ sink.write_line ""
29
+ sink.write_line key3
30
+ sink.flush
31
+
32
+ while ( ! source.eof? )
33
+ line = source.getline
34
+ break if ( line.strip == '' )
35
+ end
36
+
37
+ challenge = source.getbytes( 16 )
38
+ source.getline
39
+
40
+ if ( challenge == solution )
41
+ #
42
+ end
43
+ end
44
+
45
+ def generate_keys()
46
+ key1 = generate_header_key
47
+ key2 = generate_header_key
48
+ key3 = generate_content_key
49
+ [ key1, key2, key3, solve( key1, key2, key3 ) ]
50
+ end
51
+
52
+ def solve(key1, key2, key3)
53
+ int1 = solve_header_key( key1 )
54
+ int2 = solve_header_key( key2 )
55
+ input = int1.to_s + int2.to_s + key3
56
+ Digest::MD5.digest( input ).bytes.to_a
57
+ end
58
+
59
+ def solve_header_key(key)
60
+ key_digits = key.strip.gsub( /[^0-9]/, '').to_i
61
+ key_spaces = key.strip.gsub( /[^ ]/, '').size
62
+ solution = key_digits / key_spaces
63
+ solution
64
+ end
65
+
66
+ def generate_header_key
67
+ key = ''
68
+ 1.upto(32) do
69
+ key << rand(90) + 32
70
+ end
71
+ 1.upto( rand(10) + 2 ) do
72
+ key[rand(key.size-1)+1,1] = ' '
73
+ end
74
+ 1.upto( rand(10) + 2 ) do
75
+ key[rand(key.size-1)+1,1] = rand(9).to_s
76
+ end
77
+ key
78
+ end
79
+
80
+ def generate_content_key
81
+ 'tacobob1'
82
+ end
83
+
84
+ end
85
+ end
86
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: websocket-client
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 9
10
- version: 0.1.9
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - The TorqueBox Team
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-11 00:00:00 Z
18
+ date: 2011-06-12 00:00:00 Z
19
19
  dependencies: []
20
20
 
21
21
  description:
@@ -27,8 +27,11 @@ extensions: []
27
27
  extra_rdoc_files: []
28
28
 
29
29
  files:
30
+ - lib/websocket_client/byte_sink.rb
31
+ - lib/websocket_client/byte_source.rb
30
32
  - lib/websocket_client/client.rb
31
- - lib/websocket_client/ietf_00.rb
33
+ - lib/websocket_client/frame_io.rb
34
+ - lib/websocket_client/protocol/ietf_00.rb
32
35
  - lib/websocket_client.rb
33
36
  - examples/simple.rb
34
37
  - examples/with_block.rb
@@ -1,123 +0,0 @@
1
- require 'socket'
2
- require 'uri'
3
- require 'digest/md5'
4
-
5
- module WebSocketClient
6
-
7
- class Ietf00
8
-
9
- def initialize(uri, socket)
10
- puts "initialize for #{socket}"
11
- @socket = socket
12
- key1, key2, key3, solution = generate_keys()
13
-
14
- puts "> sending prolog"
15
- @socket.puts "GET #{uri.path} HTTP/1.1"
16
- @socket.puts "Host: #{uri.host}"
17
- @socket.puts "Connection: upgrade"
18
- @socket.puts "Upgrade: websocket"
19
- @socket.puts "Origin: http://#{uri.host}/"
20
- @socket.puts "Sec-WebSocket-Version: 8"
21
- @socket.puts "Sec-WebSocket-Key1: #{key1}"
22
- @socket.puts "Sec-WebSocket-Key2: #{key2}"
23
- @socket.puts ""
24
- @socket.print key3
25
- @socket.puts ""
26
- @socket.flush
27
-
28
- while ( ! @socket.eof? )
29
- line = readline( socket )
30
- puts ">> #{line}"
31
- break if ( line.strip == '' )
32
- end
33
-
34
- begin
35
- challenge = @socket.read( 16 )
36
- puts "Challenge: #{challenge}"
37
- readline( @socket )
38
- puts "read"
39
- rescue Exception=>e
40
- puts e.message
41
- puts e.backtrace
42
- end
43
-
44
- puts "> challenge-response #{challenge}"
45
- if ( challenge == solution )
46
- puts "success!"
47
- end
48
- #dump 'solution', solution
49
- #dump 'challenge', challenge
50
- end
51
-
52
- def readline(socket)
53
- line = ''
54
- while ( ! socket.eof? )
55
- if ( socket.respond_to? :getbyte )
56
- c = socket.getbyte
57
- else
58
- c = socket.getc
59
- end
60
- puts "> #{c} #{c.class} #{c == '\n'}"
61
-
62
- line << c
63
-
64
- if ( c == 10 )
65
- break
66
- end
67
- end
68
- line
69
- end
70
-
71
- def encode_text_message(msg)
72
- @socket.putc 0x00
73
- @socket.print msg
74
- @socket.putc 0xFF
75
- @socket.flush
76
- end
77
-
78
- def generate_keys()
79
- key1 = generate_header_key
80
- key2 = generate_header_key
81
- key3 = generate_content_key
82
- [ key1, key2, key3, solve( key1, key2, key3 ) ]
83
- end
84
-
85
- def solve(key1, key2, key3)
86
- int1 = solve_header_key( key1 )
87
- int2 = solve_header_key( key2 )
88
- input = int1.to_s + int2.to_s + key3
89
- Digest::MD5.digest( input )
90
- end
91
-
92
- def solve_header_key(key)
93
- key_digits = key.strip.gsub( /[^0-9]/, '').to_i
94
- key_spaces = key.strip.gsub( /[^ ]/, '').size
95
- solution = key_digits / key_spaces
96
- solution
97
- end
98
-
99
- def generate_header_key
100
- key = ''
101
- 1.upto(32) do
102
- key << rand(90) + 32
103
- end
104
- 1.upto( rand(10) + 2 ) do
105
- key[rand(key.size-1)+1,1] = ' '
106
- end
107
- 1.upto( rand(10) + 2 ) do
108
- key[rand(key.size-1)+1,1] = rand(9).to_s
109
- end
110
- key
111
- end
112
-
113
- def generate_content_key
114
- key = []
115
- 'tacobob1'.each_byte do |byte|
116
- key << byte
117
- end
118
- key.pack('cccccccc')
119
- end
120
-
121
- end
122
-
123
- end