em-websocket 0.0.4 → 0.0.5

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.
@@ -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