em-websocket-request 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ *.gem
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "lib/em-ws-request/vendor/web-socket-ruby"]
2
+ path = lib/em-ws-request/vendor/web-socket-ruby
3
+ url = git@github.com:rykov/web-socket-ruby.git
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in em-websocket-request.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "rspec", "~> 2.6.0"
8
+ gem "em-websocket", "~> 0.3.1"
9
+ end
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+ task :default => :spec
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/em-ws-request/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Michael Rykov"]
6
+ gem.email = ["mrykov@gmail.com"]
7
+ gem.description = %q{EventMachine WebSocket client}
8
+ gem.summary = %q{EventMachine WebSocket client}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "em-websocket-request"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = EventMachine::WS_REQUEST_VERSION
17
+
18
+ gem.add_runtime_dependency 'em-http-request', '~> 1.0.0'
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'em-http-request'
2
+ require "em-ws-request/version"
3
+ require "em-ws-request/client"
4
+ require "em-ws-request/wrapper"
5
+
6
+ module EventMachine
7
+ class WebsocketRequest < HttpRequest
8
+ def self.new(uri, options={})
9
+ connopt = HttpConnectionOptions.new(uri, options)
10
+ c = WebsocketConnection.new
11
+ c.connopts = connopt
12
+ c.uri = uri
13
+ c
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,87 @@
1
+ module EventMachine
2
+ class WebsocketConnection < HttpConnection
3
+ def setup_request(method, options = {}, c = nil)
4
+ c ||= WebsocketClient.new(self, HttpClientOptions.new(@uri, options, method))
5
+ @deferred ? activate_connection(c) : finalize_request(c)
6
+ c
7
+ end
8
+
9
+ def post_init
10
+ super
11
+ @p.on_message_complete = proc do
12
+ if not client.continue?
13
+ if client.state == :websocket
14
+ client.state = :stream
15
+ else
16
+ client.state = :finished
17
+ client.on_request_complete
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def receive_data(data)
24
+ if client.state == :stream
25
+ client.receive(data)
26
+ else
27
+ super(data)
28
+ end
29
+ end
30
+ end
31
+
32
+ class WebsocketClient < HttpClient
33
+ PROTOCOL_VERSION = '8'
34
+
35
+ def initialize(conn, options)
36
+ super(conn, options)
37
+ @wswrapper = WebSocketWrapper.new(self, PROTOCOL_VERSION)
38
+ end
39
+
40
+ def send(data, frame_type = :text)
41
+ @connection = @conn
42
+ if state == :stream || state == :websocket # FIXME
43
+ @wswrapper.send_frame(frame_type, data)
44
+ end
45
+ end
46
+
47
+ def send_data(data)
48
+ @conn.send_data(data)
49
+ end
50
+
51
+ def receive(data)
52
+ out = @wswrapper.receive(data)
53
+ @stream.call(out) if @stream
54
+ end
55
+
56
+ def websocket?
57
+ @req.uri.scheme == 'ws' || @req.uri.scheme == 'wss'
58
+ end
59
+
60
+ def build_request
61
+ head = super
62
+ if websocket?
63
+ head.delete_if { |k, v| !%w(host).include?(k) }
64
+ head['upgrade'] = 'websocket'
65
+ head['connection'] = 'Upgrade'
66
+ head['origin'] = @req.uri.host
67
+ head['Sec-WebSocket-Key'] = 'dGhlIHNhbXBsZSBub25jZQ=='
68
+ head['Sec-WebSocket-Version'] = PROTOCOL_VERSION
69
+ end
70
+ head
71
+ end
72
+
73
+ def parse_response_header(header, version, status)
74
+ super
75
+ if websocket?
76
+ p [:parse_response_header, :WEBSOCKET]
77
+ if @response_header.status == 101
78
+ @state = :websocket
79
+ succeed
80
+ else
81
+ fail "websocket handshake failed"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,3 @@
1
+ module EventMachine
2
+ WS_REQUEST_VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,46 @@
1
+ require "em-ws-request/vendor/web-socket-ruby/lib/web_socket"
2
+
3
+ module EventMachine
4
+ class WebSocketWrapper < ::WebSocket
5
+ def initialize(client, version)
6
+ @handshaked = true
7
+ @client = client
8
+ @web_socket_version = version
9
+ end
10
+
11
+ def send_frame(type, data)
12
+ opcode = type_to_opcode(type)
13
+ super(opcode, data, true)
14
+ end
15
+
16
+ def write(data)
17
+ @client.send_data(data)
18
+ end
19
+
20
+ def receive(data)
21
+ @incoming_data = data
22
+ super()
23
+ end
24
+
25
+ def read(num_bytes)
26
+ str = @incoming_data.slice(0, num_bytes)
27
+ @incoming_data = @incoming_data[num_bytes..-1]
28
+ return str
29
+ end
30
+
31
+ private
32
+ FRAME_TYPES = {
33
+ :continuation => 0,
34
+ :text => 1,
35
+ :binary => 2,
36
+ :close => 8,
37
+ :ping => 9,
38
+ :pong => 10,
39
+ }
40
+
41
+ def type_to_opcode(frame_type)
42
+ FRAME_TYPES[frame_type] || raise("Unknown frame type")
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,86 @@
1
+ require 'em-websocket'
2
+ require 'em-websocket-request'
3
+
4
+ describe EventMachine::WebsocketRequest do
5
+
6
+ context "websocket connection" do
7
+ # Spec: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55
8
+ #
9
+ # ws.onopen = http.callback
10
+ # ws.onmessage = http.stream { |msg| }
11
+ # ws.errback = no connection
12
+ #
13
+
14
+ it "should invoke errback on failed upgrade" do
15
+ EventMachine.run {
16
+ http = EventMachine::WebsocketRequest.new('ws://127.0.0.1:8090/').get :timeout => 0
17
+
18
+ http.callback { failed(http) }
19
+ http.errback {
20
+ http.response_header.status.should == 0
21
+ EventMachine.stop
22
+ }
23
+ }
24
+ end
25
+
26
+ it "should complete websocket handshake and transfer data from client to server and back" do
27
+ EventMachine.run {
28
+ MSG = "hello bi-directional data exchange"
29
+
30
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085, :debug => true) do |ws|
31
+ ws.onopen { p [:OPENED_WS, ws]}
32
+ ws.onmessage {|msg| ws.send msg}
33
+ ws.onerror {|e| p [:WS_ERROR, e]}
34
+ ws.onclose { p [:WS_CLOSE, ws]}
35
+ end
36
+
37
+ http = EventMachine::WebsocketRequest.new('ws://127.0.0.1:8085/').get :keepalive => true
38
+ http.errback { failed(http) }
39
+ http.callback {
40
+ http.response_header.status.should == 101
41
+ http.response_header['CONNECTION'].should match(/Upgrade/)
42
+ http.response_header['UPGRADE'].should match(/websocket/)
43
+
44
+ # push should only be invoked after handshake is complete
45
+ http.send(MSG)
46
+ }
47
+
48
+ http.stream { |chunk|
49
+ chunk.should == MSG
50
+ EventMachine.stop
51
+ }
52
+ }
53
+ end
54
+
55
+ # parser eats up the trailign data
56
+ # [:receive, "HTTP/1.1 101 Web Socket Protocol Handshake\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nWebSocket-Origin: 127.0.0.1\r\nWebSocket-Location: ws://127.0.0.1:8085/\r\n\r\n\x001\xFF\x002\xFF", :keep_alive?, "#<HTTP::Parser:0x0000010132d000>"]
57
+
58
+ xit "should split multiple messages from websocket server into separate stream callbacks" do
59
+ EM.run do
60
+ messages = %w[1 2]
61
+ recieved = []
62
+
63
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
64
+ ws.onopen {
65
+ ws.send messages[0]
66
+ ws.send messages[1]
67
+ }
68
+ end
69
+
70
+ EventMachine.add_timer(0.1) do
71
+ http = EventMachine::WebsocketRequest.new('ws://127.0.0.1:8085/').get :keepalive => true
72
+ http.errback { failed(http) }
73
+ http.callback { http.response_header.status.should == 101; p 'WS CONNECTED' }
74
+ http.stream {|msg|
75
+ p ['GOT MSG ', msg]
76
+ msg.should == messages[recieved.size]
77
+ recieved.push msg
78
+ p [:MULTI_MESAGE, recieved]
79
+
80
+ EventMachine.stop if recieved.size == messages.size
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-websocket-request
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Rykov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-12 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: em-http-request
16
+ requirement: &70329779717660 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70329779717660
25
+ description: EventMachine WebSocket client
26
+ email:
27
+ - mrykov@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - .gitmodules
34
+ - Gemfile
35
+ - Rakefile
36
+ - em-websocket-request.gemspec
37
+ - lib/em-websocket-request.rb
38
+ - lib/em-ws-request/client.rb
39
+ - lib/em-ws-request/version.rb
40
+ - lib/em-ws-request/wrapper.rb
41
+ - spec/websocket_spec.rb
42
+ homepage: ''
43
+ licenses: []
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 1.8.11
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: EventMachine WebSocket client
66
+ test_files:
67
+ - spec/websocket_spec.rb