em-websocket 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ *.gemspec
2
+ pkg
3
+
@@ -1,6 +1,7 @@
1
1
  = EM-WebSocket
2
2
 
3
- EventMachine based WebSocket server -- highly experimental, at this point, at least.
3
+ EventMachine based, async WebSocket server. Take a look at examples directory, or
4
+ check out the blog post below:
4
5
  - http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/
5
6
 
6
7
  == Simple server example
@@ -23,4 +24,32 @@ EventMachine based WebSocket server -- highly experimental, at this point, at le
23
24
  end
24
25
  }
25
26
 
27
+ == Examples
28
+ - examples/multicast.rb - broadcast all ruby tweets to all subscribers
29
+ - examples/echo.rb - server <> client exchange via a websocket
30
+ - http://github.com/rubenfonseca/twitter-amqp-websocket-example
26
31
 
32
+ == License
33
+
34
+ (The MIT License)
35
+
36
+ Copyright (c) 2009 Ilya Grigorik
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining
39
+ a copy of this software and associated documentation files (the
40
+ 'Software'), to deal in the Software without restriction, including
41
+ without limitation the rights to use, copy, modify, merge, publish,
42
+ distribute, sublicense, and/or sell copies of the Software, and to
43
+ permit persons to whom the Software is furnished to do so, subject to
44
+ the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be
47
+ included in all copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
50
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rake'
2
+ require 'spec/rake/spectask'
2
3
 
3
4
  begin
4
5
  require 'jeweler'
@@ -10,11 +11,21 @@ begin
10
11
  gemspec.homepage = "http://github.com/igrigorik/em-websocket"
11
12
  gemspec.authors = ["Ilya Grigorik"]
12
13
  gemspec.add_dependency("eventmachine", ">= 0.12.9")
13
- gemspec.add_dependency("addressable")
14
+ gemspec.add_dependency("addressable", '>= 2.1.1')
15
+ gemspec.add_development_dependency('em-http-request', '>= 0.2.6')
14
16
  gemspec.rubyforge_project = "em-websocket"
15
17
  end
16
18
 
17
19
  Jeweler::GemcutterTasks.new
18
20
  rescue LoadError
19
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
21
+ puts "Jeweler not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
20
22
  end
23
+
24
+ task :default => :spec
25
+
26
+ Spec::Rake::SpecTask.new do |t|
27
+ t.ruby_opts = ['-rtest/unit']
28
+ t.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
@@ -0,0 +1,43 @@
1
+ require 'lib/em-websocket'
2
+ require 'twitter/json_stream'
3
+ require 'json'
4
+
5
+ #
6
+ # broadcast all ruby related tweets to all connected users!
7
+ #
8
+
9
+ username = ARGV.shift
10
+ password = ARGV.shift
11
+ raise "need username and password" if !username or !password
12
+
13
+ EventMachine.run {
14
+ @channel = EM::Channel.new
15
+
16
+ @twitter = Twitter::JSONStream.connect(
17
+ :path => '/1/statuses/filter.json?track=ruby',
18
+ :auth => "#{username}:#{password}"
19
+ )
20
+
21
+ @twitter.each_item do |status|
22
+ status = JSON.parse(status)
23
+ @channel.push "#{status['user']['screen_name']}: #{status['text']}"
24
+ end
25
+
26
+
27
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) do |ws|
28
+ ws.onopen {
29
+ @sid = @channel.subscribe { |msg| ws.send msg }
30
+ @channel.push "#{@sid} connected!"
31
+ }
32
+
33
+ ws.onmessage { |msg|
34
+ @channel.push "<#{@sid}>: #{msg}"
35
+ }
36
+
37
+ ws.onclose {
38
+ @channel.unsubscribe(@sid)
39
+ }
40
+ end
41
+
42
+ puts "Server started"
43
+ }
@@ -1,6 +1,6 @@
1
1
  $:.unshift(File.dirname(__FILE__) + '/../lib')
2
2
 
3
- require "rubygems"
3
+ #require "rubygems"
4
4
  require "eventmachine"
5
5
 
6
6
  %w[ websocket connection ].each do |file|
@@ -7,7 +7,7 @@ module EventMachine
7
7
  PATH = /^GET (\/[^\s]*) HTTP\/1\.1$/
8
8
  HEADER = /^([^:]+):\s*([^$]+)/
9
9
 
10
- attr_reader :state
10
+ attr_reader :state, :request
11
11
 
12
12
  # define WebSocket callbacks
13
13
  def onopen(&blk); @onopen = blk; end
@@ -33,7 +33,7 @@ module EventMachine
33
33
 
34
34
  def unbind
35
35
  debug [:unbind, :connection]
36
-
36
+
37
37
  @state = :closed
38
38
  @onclose.call if @onclose
39
39
  end
@@ -59,6 +59,9 @@ module EventMachine
59
59
  # extract request path
60
60
  @request['Path'] = lines.shift.match(PATH)[1].strip
61
61
 
62
+ # extract query string values
63
+ @request['Query'] = Addressable::URI.parse(@request['Path']).query_values ||= {}
64
+
62
65
  # extract remaining headers
63
66
  lines.each do |line|
64
67
  h = HEADER.match(line)
@@ -67,7 +70,7 @@ module EventMachine
67
70
 
68
71
  # transform headers
69
72
  @request['Host'] = Addressable::URI.parse("ws://"+@request['Host'])
70
-
73
+
71
74
  if not websocket_connection?
72
75
  send_data "HTTP/1.1 400 Bad request\r\n\r\n"
73
76
  close_connection_after_writing
@@ -90,7 +93,7 @@ module EventMachine
90
93
  def send_upgrade
91
94
  location = "ws://#{@request['Host'].host}"
92
95
  location << ":#{@request['Host'].port}" if @request['Host'].port
93
- location << @request['Path']
96
+ location << @request['Path'].split("?").first
94
97
 
95
98
  upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
96
99
  upgrade << "Upgrade: WebSocket\r\n"
@@ -120,7 +123,7 @@ module EventMachine
120
123
  msg.gsub!(/^\x00|\xff$/, '')
121
124
  @onmessage.call(msg)
122
125
  end
123
-
126
+
124
127
  false
125
128
  end
126
129
 
@@ -11,7 +11,7 @@ describe EventMachine::WebSocket do
11
11
  EM.run do
12
12
  MSG = "Hello World!"
13
13
  EventMachine.add_timer(0.1) do
14
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
14
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
15
15
  http.errback { failed }
16
16
  http.callback { http.response_header.status.should == 101 }
17
17
 
@@ -21,7 +21,7 @@ describe EventMachine::WebSocket do
21
21
  }
22
22
  end
23
23
 
24
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
24
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
25
25
  ws.onopen {
26
26
  puts "WebSocket connection open"
27
27
  ws.send MSG
@@ -33,7 +33,7 @@ describe EventMachine::WebSocket do
33
33
  it "should fail on non WebSocket requests" do
34
34
  EM.run do
35
35
  EventMachine.add_timer(0.1) do
36
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :timeout => 0
36
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
37
37
  http.errback { failed }
38
38
  http.callback {
39
39
  http.response_header.status.should == 400
@@ -41,17 +41,17 @@ describe EventMachine::WebSocket do
41
41
  }
42
42
  end
43
43
 
44
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) {}
44
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) {}
45
45
  end
46
46
  end
47
47
 
48
48
  it "should split multiple messages into separate callbacks" do
49
49
  EM.run do
50
50
  messages = %w[1 2]
51
- recieved = []
51
+ received = []
52
52
 
53
53
  EventMachine.add_timer(0.1) do
54
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
54
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
55
55
  http.errback { failed }
56
56
  http.stream {|msg|}
57
57
  http.callback {
@@ -61,14 +61,14 @@ describe EventMachine::WebSocket do
61
61
  }
62
62
  end
63
63
 
64
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
64
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
65
65
  ws.onopen {}
66
66
  ws.onclose {}
67
67
  ws.onmessage {|msg|
68
- msg.should == messages[recieved.size]
69
- recieved.push msg
68
+ msg.should == messages[received.size]
69
+ received.push msg
70
70
 
71
- EventMachine.stop if recieved.size == messages.size
71
+ EventMachine.stop if received.size == messages.size
72
72
  }
73
73
  end
74
74
  end
@@ -77,7 +77,7 @@ describe EventMachine::WebSocket do
77
77
  it "should call onclose callback when client closes connection" do
78
78
  EM.run do
79
79
  EventMachine.add_timer(0.1) do
80
- http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
80
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
81
81
  http.errback { failed }
82
82
  http.callback {
83
83
  http.response_header.status.should == 101
@@ -86,7 +86,7 @@ describe EventMachine::WebSocket do
86
86
  http.stream{|msg|}
87
87
  end
88
88
 
89
- EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
89
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
90
90
  ws.onopen {}
91
91
  ws.onclose {
92
92
  ws.state.should == :closed
@@ -96,4 +96,84 @@ describe EventMachine::WebSocket do
96
96
  end
97
97
  end
98
98
 
99
+ it "should populate ws.request with appropriate headers" do
100
+ EM.run do
101
+ EventMachine.add_timer(0.1) do
102
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
103
+ http.errback { failed }
104
+ http.callback {
105
+ http.response_header.status.should == 101
106
+ http.close_connection
107
+ }
108
+ http.stream { |msg| }
109
+ end
110
+
111
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
112
+ ws.onopen {
113
+ ws.request["User-Agent"].should == "EventMachine HttpClient"
114
+ ws.request["Connection"].should == "Upgrade"
115
+ ws.request["Upgrade"].should == "WebSocket"
116
+ ws.request["Path"].should == "/"
117
+ ws.request["Origin"].should == "127.0.0.1"
118
+ ws.request["Host"].to_s.should == "ws://127.0.0.1:12345"
119
+ }
120
+ ws.onclose {
121
+ ws.state.should == :closed
122
+ EventMachine.stop
123
+ }
124
+ end
125
+ end
126
+ end
127
+
128
+ it "should allow sending and retrieving query string args passed in on the connection request." do
129
+ EM.run do
130
+ EventMachine.add_timer(0.1) do
131
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:query => {'foo' => 'bar', 'baz' => 'qux'}, :timeout => 0)
132
+ http.errback { failed }
133
+ http.callback {
134
+ http.response_header.status.should == 101
135
+ http.close_connection
136
+ }
137
+ http.stream { |msg| }
138
+ end
139
+
140
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
141
+ ws.onopen {
142
+ ws.request["Path"].should == "/?baz=qux&foo=bar"
143
+ ws.request["Query"]["foo"].should == "bar"
144
+ ws.request["Query"]["baz"].should == "qux"
145
+ }
146
+ ws.onclose {
147
+ ws.state.should == :closed
148
+ EventMachine.stop
149
+ }
150
+ end
151
+ end
152
+ end
153
+
154
+ it "should ws.response['Query'] to empty hash when no query string params passed in connection URI" do
155
+ EM.run do
156
+ EventMachine.add_timer(0.1) do
157
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0)
158
+ http.errback { failed }
159
+ http.callback {
160
+ http.response_header.status.should == 101
161
+ http.close_connection
162
+ }
163
+ http.stream { |msg| }
164
+ end
165
+
166
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
167
+ ws.onopen {
168
+ ws.request["Path"].should == "/"
169
+ ws.request["Query"].should == {}
170
+ }
171
+ ws.onclose {
172
+ ws.state.should == :closed
173
+ EventMachine.stop
174
+ }
175
+ end
176
+ end
177
+ end
178
+
99
179
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Grigorik
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-24 00:00:00 -05:00
12
+ date: 2010-01-06 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,17 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: "0"
33
+ version: 2.1.1
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: em-http-request
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.2.6
34
44
  version:
35
45
  description: EventMachine based WebSocket server
36
46
  email: ilya@igvita.com
@@ -41,10 +51,12 @@ extensions: []
41
51
  extra_rdoc_files:
42
52
  - README.rdoc
43
53
  files:
54
+ - .gitignore
44
55
  - README.rdoc
45
56
  - Rakefile
46
57
  - VERSION
47
58
  - examples/echo.rb
59
+ - examples/multicast.rb
48
60
  - examples/test.html
49
61
  - lib/em-websocket.rb
50
62
  - lib/em-websocket/connection.rb
@@ -83,3 +95,4 @@ test_files:
83
95
  - spec/helper.rb
84
96
  - spec/websocket_spec.rb
85
97
  - examples/echo.rb
98
+ - examples/multicast.rb