em-websocket-request 0.0.1

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