websocket-client 0.1.9 → 0.2.0

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