async-websocket 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ba6df1f81d1727db806e398e804b402a821f138e46298fe501c49462a68d5fd
4
- data.tar.gz: 898bd7a5f9dc2ca60b4d0b4f3ca94a8e5488a18161543002a0cbf5b397d5d7d0
3
+ metadata.gz: 43a7d40f381a9a3874e3fda412627762d30d2354975e94b6550bda29f75fd26d
4
+ data.tar.gz: 197f5ba2209b7bcc8d2eec2e600265ca1b5e59b264967a64da7f416761b9c6a4
5
5
  SHA512:
6
- metadata.gz: bc6798196531c1994e077f41c090851c7e3de09347672f26fd913a5a4e6ebf97986d3113d0caa4a49de56060104ebbbe4528854fa77b4dfcf6d72a5d1d27a6be
7
- data.tar.gz: 29693d7fe7ae34a21a5105d4231512181f611bfd51ff25e7a66b2feba53a309aee952bc5e6bf9d95b9d01a51ecc83b873275911a096786032dd6e090a170e637
6
+ metadata.gz: 26a221435af1324568f92f7ab2317c16add07196a8eeca36108d226cfccf79cc5f8ad264c3a22b41cf245725e3bb20c148b37e3d71b644bd8ea4ff1373e149e0
7
+ data.tar.gz: 982f6ca5dce50b3e230850d298f23cf1410c7c084a503ca52da60b6edcde2efc0f2e9143a500a165de482b72fb32cdfd1b6c71434e44be07b36c6669001195e1
data/README.md CHANGED
@@ -24,7 +24,8 @@ Or install it yourself as:
24
24
 
25
25
  There are [examples](examples/) which include:
26
26
 
27
- - [A command line chat client and application server](examples/chat/client.rb) which can read input from `stdin` and send messages to the server.
27
+ - [A command line chat client/server](examples/chat) which can read input from `stdin` and send messages to the server.
28
+ - [A small MUD client/server](examples/mud) which uses JSON for communation between client/server.
28
29
  - [A utopia-based web application](examples/utopia) which uses a JavaScript client to connect to a web application server.
29
30
 
30
31
  ### Client Side with Async
@@ -38,23 +39,33 @@ require 'async/http/url_endpoint'
38
39
  require 'async/websocket/client'
39
40
 
40
41
  USER = ARGV.pop || "anonymous"
41
- URL = ARGV.pop || "ws://localhost:9292"
42
+ URL = ARGV.pop || "http://localhost:7070"
42
43
 
43
44
  Async do |task|
45
+ stdin = Async::IO::Stream.new(
46
+ Async::IO::Generic.new($stdin)
47
+ )
48
+
44
49
  endpoint = Async::HTTP::URLEndpoint.parse(URL)
45
- headers = {'token' => 'wubalubadubdub'}
46
50
 
47
- endpoint.connect do |socket|
48
- connection = Async::WebSocket::Client.new(socket, URL, headers)
51
+ Async::WebSocket::Client.open(endpoint) do |connection|
52
+ input_task = task.async do
53
+ while line = stdin.read_until("\n")
54
+ connection.write({user: USER, text: line})
55
+ connection.flush
56
+ end
57
+ end
49
58
 
50
- connection.send_message({
59
+ connection.write({
51
60
  user: USER,
52
61
  status: "connected",
53
62
  })
54
63
 
55
- while message = connection.next_message
64
+ while message = connection.read
56
65
  puts message.inspect
57
66
  end
67
+ ensure
68
+ input_task&.stop
58
69
  end
59
70
  end
60
71
  ```
@@ -62,26 +73,26 @@ end
62
73
  ### Server Side with Rack & Falcon
63
74
 
64
75
  ```ruby
65
- #!/usr/bin/env falcon serve --concurrency 1 -c
76
+ #!/usr/bin/env -S falcon serve --bind http://localhost:7070 --count 1 -c
66
77
 
67
- require 'async/websocket/server'
78
+ require 'async/websocket/server/rack'
79
+ require 'set'
68
80
 
69
- $connections = []
81
+ $connections = Set.new
70
82
 
71
83
  run lambda {|env|
72
- # Options for websocket-driver-ruby can be passed as second argument to open
73
- # Supported options here: https://github.com/faye/websocket-driver-ruby#driver-api
74
- Async::WebSocket::Server.open(env, protocols: ['ws']) do |connection|
84
+ Async::WebSocket::Server::Rack.open(env, protocols: ['ws']) do |connection|
75
85
  $connections << connection
76
86
 
77
- while message = connection.next_message
87
+ while message = connection.read
78
88
  $connections.each do |connection|
79
- connection.send_message(message)
89
+ connection.write(message)
90
+ connection.flush
80
91
  end
81
92
  end
82
- end
83
-
84
- [200, {}, ["Hello World"]]
93
+ ensure
94
+ $connections.delete(connection)
95
+ end or [200, {}, ["Hello World"]]
85
96
  }
86
97
  ```
87
98
 
@@ -16,8 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.require_paths = ["lib"]
17
17
 
18
18
  spec.add_dependency "async-io", "~> 1.23"
19
- spec.add_dependency "protocol-http1", "~> 0.1"
20
- spec.add_dependency "protocol-websocket", "~> 0.3"
19
+ spec.add_dependency "protocol-http1", "~> 0.4"
20
+ spec.add_dependency "protocol-websocket", "~> 0.5.0"
21
21
 
22
22
  spec.add_development_dependency "async-rspec"
23
23
  spec.add_development_dependency "falcon", "~> 0.30"
@@ -17,13 +17,13 @@ Async do |task|
17
17
  Async::WebSocket::Client.open(ENDPOINT) do |connection|
18
18
  input_task = task.async do
19
19
  while line = stdin.read_until("\n")
20
- connection.send_message({text: line})
20
+ connection.write({text: line})
21
21
  connection.flush
22
22
  end
23
23
  end
24
24
 
25
25
  puts "Connected..."
26
- while message = connection.next_message
26
+ while message = connection.read
27
27
  puts "> #{message.inspect}"
28
28
  end
29
29
  ensure
@@ -70,7 +70,8 @@ class Room
70
70
 
71
71
  @connections.each do |connection|
72
72
  @semaphore.async do
73
- connection.send_message(message)
73
+ connection.write(message)
74
+ connection.flush
74
75
  end
75
76
  end
76
77
 
@@ -81,18 +82,18 @@ class Room
81
82
  def open(connection)
82
83
  self.connect(connection)
83
84
 
84
- while message = connection.next_message
85
- if message["text"] =~ /^\/(.*?)$/
85
+ while message = connection.read
86
+ if message[:text] =~ /^\/(.*?)$/
86
87
  begin
87
88
  result = self.command($1)
88
89
 
89
90
  if result.is_a? Hash
90
- connection.send_message(result)
91
+ connection.write(result)
91
92
  else
92
- connection.send_message({result: result.inspect})
93
+ connection.write({result: result.inspect})
93
94
  end
94
95
  rescue
95
- connection.send_message({error: $!.inspect})
96
+ connection.write({error: $!.inspect})
96
97
  end
97
98
  else
98
99
  self.broadcast(message)
@@ -45,9 +45,9 @@ class Command < Samovar::Command
45
45
  task.async do |subtask|
46
46
  while connection = connections.dequeue
47
47
  subtask.async(connection) do |subtask, connection|
48
- pp connection.next_message
48
+ pp connection.read
49
49
 
50
- while message = connection.next_message
50
+ while message = connection.read
51
51
  pp message
52
52
  end
53
53
  ensure
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async'
4
+ require 'async/io/stream'
5
+ require 'async/http/url_endpoint'
6
+ require 'async/websocket/client'
7
+
8
+ USER = ARGV.pop || "anonymous"
9
+ URL = ARGV.pop || "http://127.0.0.1:7070"
10
+
11
+ Async do |task|
12
+ stdin = Async::IO::Stream.new(
13
+ Async::IO::Generic.new($stdin)
14
+ )
15
+
16
+ endpoint = Async::HTTP::URLEndpoint.parse(URL)
17
+
18
+ Async::WebSocket::Client.open(endpoint) do |connection|
19
+ task.async do
20
+ $stdout.write "> "
21
+
22
+ while line = stdin.read_until("\n")
23
+ connection.write({input: line})
24
+ connection.flush
25
+
26
+ $stdout.write "> "
27
+ end
28
+ end
29
+
30
+ while message = connection.read
31
+ $stdout.puts message
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env falcon serve --count 1 --bind http://127.0.0.1:7070 -c
2
+
3
+ require 'async/websocket/server/rack'
4
+
5
+ class Room
6
+ def initialize(name, description = nil)
7
+ @name = name
8
+ @description = description
9
+
10
+ @actions = {}
11
+ @users = []
12
+ end
13
+
14
+ attr :name
15
+ attr :description
16
+
17
+ attr :actions
18
+
19
+ def connect(key, room)
20
+ @actions[key] = lambda do |user|
21
+ self.exit(user)
22
+ room.enter(user)
23
+ end
24
+
25
+ return room
26
+ end
27
+
28
+ def broadcast(message)
29
+ @users.each do |user|
30
+ user.write(message)
31
+ user.flush
32
+ end
33
+ end
34
+
35
+ def enter(user)
36
+ user.notify("You have entered the #{@name}.")
37
+ user.room = self
38
+
39
+ @users << user
40
+
41
+ @users.each do |user|
42
+ user.notify("#{user.name} entered the room.")
43
+ end
44
+ end
45
+
46
+ def exit(user)
47
+ if @users.delete(user)
48
+ @users.each do |user|
49
+ user.notify("#{user.name} left the room.")
50
+ end
51
+ end
52
+ end
53
+
54
+ def as_json
55
+ {
56
+ name: @name,
57
+ description: @description,
58
+ actions: @actions.keys,
59
+ }
60
+ end
61
+ end
62
+
63
+ module Command
64
+ def self.split(line)
65
+ line.scan(/(?:"")|(?:"(.*[^\\])")|(\w+)/).flatten.compact
66
+ end
67
+ end
68
+
69
+ class User < Async::WebSocket::Connection
70
+ def initialize(*)
71
+ super
72
+
73
+ @name = name
74
+ @room = nil
75
+ @inventory = []
76
+ end
77
+
78
+ attr_accessor :room
79
+
80
+ ANONYMOUS = "Anonymous"
81
+
82
+ def name
83
+ @name || ANONYMOUS
84
+ end
85
+
86
+ def handle(message)
87
+ key, *arguments = Command.split(message[:input])
88
+ case key
89
+ when "name"
90
+ @name = arguments.first
91
+ when "look"
92
+ self.write({room: @room.as_json})
93
+ else
94
+ if action = @room.actions[key]
95
+ action.call(self, *arguments)
96
+ else
97
+ message[:user]
98
+ @room.broadcast(message)
99
+ end
100
+ end
101
+ end
102
+
103
+ def notify(text)
104
+ self.write({notify: text})
105
+ self.flush
106
+ end
107
+
108
+ def close
109
+ if @room
110
+ @room.exit(self)
111
+ end
112
+
113
+ super
114
+ end
115
+ end
116
+
117
+ class Server
118
+ def initialize(app)
119
+ @app = app
120
+
121
+ @entrance = Room.new("Portal", "A vast entrance foyer with a flowing portal.")
122
+ @entrance.connect("forward", Room.new("Training Room"))
123
+ @entrance.connect("corridor", Room.new("Shop"))
124
+ end
125
+
126
+ def call(env)
127
+ Async::WebSocket::Server::Rack.open(env, connect: User) do |user|
128
+ @entrance.enter(user)
129
+
130
+ while message = user.read
131
+ user.handle(message)
132
+ end
133
+ ensure
134
+ # Async.logger.error(self, $!) if $!
135
+ user.close
136
+ end or @app.call(env)
137
+ end
138
+ end
139
+
140
+ use Server
141
+
142
+ run lambda {|env| [200, {}, []]}
@@ -6,16 +6,14 @@ require 'async/websocket/server'
6
6
  $connections = []
7
7
 
8
8
  on 'connect' do |request|
9
- Async::WebSocket::Server.open(request.env) do |connection|
9
+ respond? Async::WebSocket::Server::Rack.open(request.env) do |connection|
10
10
  $connections << connection
11
11
 
12
- while message = connection.next_message
12
+ while message = connection.read
13
13
  $connections.each do |connection|
14
14
  puts "Server sending message: #{message.inspect}"
15
- connection.send_message(message)
15
+ connection.write(message)
16
16
  end
17
17
  end
18
18
  end
19
-
20
- succeed!
21
19
  end
@@ -27,8 +27,8 @@ RSpec.describe "my website" do
27
27
 
28
28
  let(:hello_message) do
29
29
  {
30
- "user" => "test",
31
- "text" => "Hello World",
30
+ user: "test",
31
+ text: "Hello World",
32
32
  }
33
33
  end
34
34
 
@@ -46,9 +46,9 @@ RSpec.describe "my website" do
46
46
  endpoint.connect do |socket|
47
47
  connection = Async::WebSocket::Client.new(socket, "ws://localhost/server/connect")
48
48
 
49
- connection.send_message(hello_message)
49
+ connection.write(hello_message)
50
50
 
51
- message = connection.next_message
51
+ message = connection.read
52
52
  expect(message).to be == hello_message
53
53
  end
54
54
  end
@@ -23,6 +23,8 @@
23
23
  require 'protocol/http1/connection'
24
24
  require 'protocol/websocket/digest'
25
25
 
26
+ require 'securerandom'
27
+
26
28
  require_relative 'connection'
27
29
  require_relative 'error'
28
30
 
@@ -40,22 +42,30 @@ module Async
40
42
  end
41
43
 
42
44
  # @option protocols [Array] a list of supported sub-protocols to negotiate with the server.
43
- def initialize(endpoint, headers: [], protocols: [], version: 13, key: SecureRandom.base64(16))
45
+ def initialize(endpoint, headers: [], protocols: [], version: 13, key: SecureRandom.base64(16), connect: Connection)
44
46
  @endpoint = endpoint
45
47
  @version = version
46
48
  @headers = headers
47
49
 
48
50
  @protocols = protocols
49
-
50
51
  @key = key
52
+
53
+ @connect = connect
54
+ end
55
+
56
+ def mask
57
+ # Mask is only required on insecure connections, because of bad proxy implementations.
58
+ unless @endpoint.secure?
59
+ SecureRandom.bytes(4)
60
+ end
51
61
  end
52
62
 
53
63
  attr :headers
54
64
 
55
65
  def connect
56
- peer = @endpoint.connect
66
+ stream = IO::Stream.new(@endpoint.connect)
57
67
 
58
- return ::Protocol::HTTP1::Connection.new(IO::Stream.new(peer), false)
68
+ return ::Protocol::HTTP1::Connection.new(stream, false)
59
69
  end
60
70
 
61
71
  def request_headers
@@ -82,7 +92,7 @@ module Async
82
92
 
83
93
  framer = Protocol::WebSocket::Framer.new(stream)
84
94
 
85
- return Connection.new(framer, protocol)
95
+ return @connect.call(framer, protocol, mask: self.mask)
86
96
  end
87
97
 
88
98
  def call(method, path)
@@ -21,7 +21,6 @@
21
21
  require 'protocol/websocket/connection'
22
22
 
23
23
  require 'json'
24
- require 'securerandom'
25
24
 
26
25
  module Async
27
26
  module WebSocket
@@ -29,30 +28,31 @@ module Async
29
28
 
30
29
  # This is a basic synchronous websocket client:
31
30
  class Connection < ::Protocol::WebSocket::Connection
32
- def initialize(framer, protocol, mask: SecureRandom.bytes(4), format: JSON)
33
- super(framer)
34
-
31
+ def self.call(framer, protocol = nil, **options)
32
+ self.new(framer, protocol, **options)
33
+ end
34
+
35
+ def initialize(framer, protocol = nil, **options)
36
+ super(framer, **options)
35
37
  @protocol = protocol
36
- @mask = mask
37
- @format = format
38
38
  end
39
39
 
40
40
  attr :protocol
41
41
 
42
- def next_message
43
- if frames = super
44
- if frames.first.is_a? Protocol::WebSocket::TextFrame
45
- buffer = frames.collect(&:unpack).join
46
-
47
- return @format.load(buffer)
48
- else
49
- return frames
50
- end
51
- end
42
+ def read
43
+ parse(super)
44
+ end
45
+
46
+ def write(object)
47
+ super(dump(object))
48
+ end
49
+
50
+ def parse(buffer)
51
+ JSON.parse(buffer, symbolize_names: true)
52
52
  end
53
53
 
54
- def send_message(data)
55
- send_text(@format.dump(data))
54
+ def dump(object)
55
+ JSON.dump(object)
56
56
  end
57
57
  end
58
58
  end
@@ -28,26 +28,34 @@ module Async
28
28
  module WebSocket
29
29
  module Server
30
30
  class Rack
31
+ def self.websocket?(env)
32
+ env['HTTP_UPGRADE'] == "websocket"
33
+ end
34
+
31
35
  def self.open(env, **options, &block)
36
+ # Is hijack supported:
32
37
  return nil unless env['rack.hijack?']
33
38
 
34
- connection = self.new(env, **options)
39
+ return nil unless websocket?(env)
40
+
41
+ server = self.new(env, **options)
35
42
 
36
- if connection.supported?
37
- return connection.response(&block)
43
+ if server.supported?
44
+ return server.response(&block)
38
45
  else
39
46
  return nil
40
47
  end
41
48
  end
42
49
 
43
- def initialize(env, supported_protocols: [], **options)
44
- scheme = env['rack.url_scheme'] == 'https' ? 'wss' : 'ws'
45
- @url = "#{scheme}://#{env['HTTP_HOST']}#{env['REQUEST_URI']}"
46
-
50
+ def initialize(env, supported_protocols: [], connect: Connection)
51
+ # scheme = env['rack.url_scheme'] == 'https' ? 'wss' : 'ws'
52
+ # @url = "#{scheme}://#{env['HTTP_HOST']}#{env['REQUEST_URI']}"
47
53
  @key = env['HTTP_SEC_WEBSOCKET_KEY']
48
54
  @version = Integer(env['HTTP_SEC_WEBSOCKET_VERSION'])
49
55
 
50
56
  @protocol = negotiate_protocol(env, supported_protocols)
57
+
58
+ @connect = connect
51
59
  end
52
60
 
53
61
  def negotiate_protocol(env, supported_protocols)
@@ -65,7 +73,7 @@ module Async
65
73
  def make_connection(stream)
66
74
  framer = Protocol::WebSocket::Framer.new(stream)
67
75
 
68
- Connection.new(framer, @protocol)
76
+ return @connect.call(framer, @protocol)
69
77
  end
70
78
 
71
79
  def response_headers
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module WebSocket
23
- VERSION = "0.9.0"
23
+ VERSION = "0.10.0"
24
24
  end
25
25
  end
@@ -18,52 +18,17 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'async/websocket'
22
- require 'async/websocket/client'
21
+ require 'async/websocket/connection'
23
22
 
24
- require 'rack/test'
25
- require 'falcon/server'
26
- require 'falcon/adapters/rack'
27
- require 'async/http/url_endpoint'
28
-
29
- RSpec.describe Async::WebSocket::Connection, timeout: nil do
30
- include_context Async::RSpec::Reactor
23
+ RSpec.describe Async::WebSocket::Connection do
24
+ let(:framer) {double}
25
+ subject {described_class.new(framer)}
31
26
 
32
- let(:server_address) {Async::HTTP::URLEndpoint.parse("http://localhost:9000")}
33
- let(:app) {Rack::Builder.parse_file(File.expand_path('../connection_spec.ru', __FILE__)).first}
34
- let(:server) {Falcon::Server.new(Falcon::Server.middleware(app, verbose: true), server_address)}
35
-
36
- it "should connect to the websocket server" do
37
- server_task = reactor.async do
38
- server.run
39
- end
40
-
41
- events = []
42
-
43
- Async::WebSocket::Client.open(server_address) do |connection|
44
- while event = connection.next_message
45
- expect(event).to include("line")
46
-
47
- events << event
48
- end
49
-
50
- connection.close # optional
51
- end
52
-
53
- expect(events.size).to be > 0
54
-
55
- server_task.stop
56
- end
57
-
58
- it "should negotiate protocol" do
59
- server_task = reactor.async do
60
- server.run
61
- end
62
-
63
- Async::WebSocket::Client.open(server_address, protocols: ['ws']) do |connection|
64
- expect(connection.protocol).to be == 'ws'
27
+ it "should use mask if specified" do
28
+ expect(framer).to receive(:write_frame) do |frame|
29
+ expect(frame.mask).to be == subject.mask
65
30
  end
66
31
 
67
- server_task.stop
32
+ subject.write({text: "Hello World"})
68
33
  end
69
34
  end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async'
4
+ require 'async/io/stream'
5
+ require 'async/http/url_endpoint'
6
+ require 'async/websocket/client'
7
+
8
+ USER = ARGV.pop || "anonymous"
9
+ URL = ARGV.pop || "http://localhost:7070"
10
+
11
+ Async do |task|
12
+ stdin = Async::IO::Stream.new(
13
+ Async::IO::Generic.new($stdin)
14
+ )
15
+
16
+ endpoint = Async::HTTP::URLEndpoint.parse(URL)
17
+ headers = {'token' => 'wubalubadubdub'}
18
+
19
+ Async::WebSocket::Client.open(endpoint, headers: headers) do |connection|
20
+ input_task = task.async do
21
+ while line = stdin.read_until("\n")
22
+ connection.write({user: USER, text: line})
23
+ connection.flush
24
+ end
25
+ end
26
+
27
+ connection.write({
28
+ user: USER,
29
+ status: "connected",
30
+ })
31
+
32
+ while message = connection.read
33
+ puts message.inspect
34
+ end
35
+ ensure
36
+ input_task&.stop
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env -S falcon serve --bind http://localhost:7070 --count 1 -c
2
+
3
+ require 'async/websocket/server/rack'
4
+ require 'set'
5
+
6
+ $connections = Set.new
7
+
8
+ run lambda {|env|
9
+ Async::WebSocket::Server::Rack.open(env, supported_protocols: ['ws']) do |connection|
10
+ $connections << connection
11
+
12
+ begin
13
+ while message = connection.read
14
+ $connections.each do |connection|
15
+ connection.write(message)
16
+ connection.flush
17
+ end
18
+ end
19
+ ensure
20
+ $connections.delete(connection)
21
+ end
22
+ end or [200, {}, ["Hello World"]]
23
+ }
@@ -0,0 +1,83 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/websocket'
22
+ require 'async/websocket/client'
23
+ require 'async/websocket/server/rack'
24
+
25
+ require 'rack/test'
26
+ require 'falcon/server'
27
+ require 'falcon/adapters/rack'
28
+ require 'async/http/url_endpoint'
29
+
30
+ RSpec.describe Async::WebSocket::Server::Rack do
31
+ include_context Async::RSpec::Reactor
32
+
33
+ let(:server_address) {Async::HTTP::URLEndpoint.parse("http://localhost:7050")}
34
+ let(:app) {Rack::Builder.parse_file(File.expand_path('rack/config.ru', __dir__)).first}
35
+ let(:server) {Falcon::Server.new(Falcon::Server.middleware(app), server_address)}
36
+ let(:client) {Async::HTTP::Client.new(server_address)}
37
+
38
+ let!(:server_task) do
39
+ reactor.async do
40
+ server.run
41
+ end
42
+ end
43
+
44
+ after do
45
+ server_task.stop
46
+ end
47
+
48
+ it "can make non-websocket connection to server" do
49
+ response = client.get("/")
50
+ expect(response).to be_success
51
+ expect(response.read).to be == "Hello World"
52
+
53
+ client.close
54
+ end
55
+
56
+ let(:message) do
57
+ {text: "Hello World"}
58
+ end
59
+
60
+ it "can make websocket connection to server" do
61
+ Async::WebSocket::Client.open(server_address) do |connection|
62
+ connection.write(message)
63
+
64
+ expect(connection.read).to be == message
65
+
66
+ connection.close
67
+ end
68
+ end
69
+
70
+ it "should use mask over insecure connection" do
71
+ expect(server_address).to_not be_secure
72
+
73
+ Async::WebSocket::Client.open(server_address) do |connection|
74
+ expect(connection.mask).to_not be_nil
75
+ end
76
+ end
77
+
78
+ it "should negotiate protocol" do
79
+ Async::WebSocket::Client.open(server_address, protocols: ['ws']) do |connection|
80
+ expect(connection.protocol).to be == 'ws'
81
+ end
82
+ end
83
+ end
@@ -33,7 +33,7 @@ class Upgrade
33
33
  write.close
34
34
 
35
35
  read.each_line do |line|
36
- connection.send_message({line: line})
36
+ connection.write({line: line})
37
37
  end
38
38
 
39
39
  # Gracefully close the connection:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-10 00:00:00.000000000 Z
11
+ date: 2019-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-io
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '0.4'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '0.4'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: protocol-websocket
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.3'
47
+ version: 0.5.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.3'
54
+ version: 0.5.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: async-rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -154,8 +154,8 @@ files:
154
154
  - examples/chat/client.rb
155
155
  - examples/chat/config.ru
156
156
  - examples/chat/multi-client.rb
157
- - examples/middleware/client.rb
158
- - examples/middleware/config.ru
157
+ - examples/mud/client.rb
158
+ - examples/mud/config.ru
159
159
  - examples/utopia/.bowerrc
160
160
  - examples/utopia/.gitignore
161
161
  - examples/utopia/.rspec
@@ -170,7 +170,6 @@ files:
170
170
  - examples/utopia/pages/_heading.xnode
171
171
  - examples/utopia/pages/_page.xnode
172
172
  - examples/utopia/pages/client/client.js
173
- - examples/utopia/pages/client/controller.rb
174
173
  - examples/utopia/pages/client/index.xnode
175
174
  - examples/utopia/pages/errors/exception.xnode
176
175
  - examples/utopia/pages/errors/file-not-found.xnode
@@ -199,7 +198,9 @@ files:
199
198
  - lib/async/websocket/version.rb
200
199
  - spec/async/websocket/client_spec.rb
201
200
  - spec/async/websocket/connection_spec.rb
202
- - spec/async/websocket/connection_spec.ru
201
+ - spec/async/websocket/server/rack/client.rb
202
+ - spec/async/websocket/server/rack/config.ru
203
+ - spec/async/websocket/server/rack_spec.rb
203
204
  - spec/async/websocket/upgrade.rb
204
205
  - spec/spec_helper.rb
205
206
  homepage: ''
@@ -228,6 +229,8 @@ summary: An async websocket library on top of websocket-driver.
228
229
  test_files:
229
230
  - spec/async/websocket/client_spec.rb
230
231
  - spec/async/websocket/connection_spec.rb
231
- - spec/async/websocket/connection_spec.ru
232
+ - spec/async/websocket/server/rack/client.rb
233
+ - spec/async/websocket/server/rack/config.ru
234
+ - spec/async/websocket/server/rack_spec.rb
232
235
  - spec/async/websocket/upgrade.rb
233
236
  - spec/spec_helper.rb
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'async'
4
- require 'async/io/stream'
5
- require 'async/http/url_endpoint'
6
- require 'async/websocket/client'
7
-
8
- USER = ARGV.pop || "anonymous"
9
- URL = ARGV.pop || "ws://localhost:9292"
10
-
11
- Async do |task|
12
- stdin = Async::IO::Stream.new(
13
- Async::IO::Generic.new($stdin)
14
- )
15
-
16
- endpoint = Async::HTTP::URLEndpoint.parse(URL)
17
-
18
- endpoint.with(local_address: local_address).connect do |socket|
19
- connection = Async::WebSocket::Client.new(socket, URL)
20
-
21
- connection.send_message({
22
- user: USER,
23
- status: "connected",
24
- })
25
-
26
- task.async do
27
- puts "Waiting for input..."
28
- begin
29
- while line = stdin.read_until("\n")
30
- puts "Sending text: #{line}"
31
- connection.send_message({
32
- user: USER,
33
- text: line,
34
- })
35
- end
36
- rescue
37
- puts "Client error: #{$!}"
38
- end
39
- end
40
-
41
- while message = connection.next_message
42
- puts "From server: #{message.inspect}"
43
- end
44
- end
45
- end
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env falcon --verbose serve --concurrency 1 -c
2
-
3
- require 'async/websocket/server'
4
-
5
- Async.logger.level = Logger::DEBUG
6
-
7
- class RackUpgrade
8
- def initialize(app)
9
- @app = app
10
- end
11
-
12
- def call(env)
13
- if ::WebSocket::Driver.websocket?(env)
14
- env['rack.upgrade?'] = :websocket
15
-
16
- response = @app.call(env)
17
-
18
- if handler = env['rack.upgrade']
19
- Async::WebSocket::Server.open(env) do |connection|
20
- begin
21
- while event = connection.next_event
22
- if event.is_a? ::WebSocket::Driver::OpenEvent
23
- handler.on_open(connection) if handler.respond_to? :on_open
24
- elsif event.is_a? ::WebSocket::Driver::MessageEvent
25
- handler.on_message(connection, JSON.parse(event.data))
26
- elsif event.is_a? ::WebSocket::Driver::CloseEvent
27
- handler.on_close(connection) if handler.respond_to? :on_close
28
- end
29
- end
30
- ensure
31
- handler.on_shutdown(connection) if handler.respond_to? :on_shutdown
32
- end
33
- end
34
- end
35
- else
36
- return @app.call(env)
37
- end
38
- end
39
- end
40
-
41
- class Chatty
42
- def initialize
43
- @connections = Set.new
44
- end
45
-
46
- def on_open(connection)
47
- Async.logger.info(self) {"on_open: #{connection}"}
48
- @connections << connection
49
- end
50
-
51
- def on_message(connection, message)
52
- Async.logger.info(self) {"on_message: #{connection} -> #{message}"}
53
-
54
- @connections.each do |connection|
55
- connection.send_message(message)
56
- end
57
- end
58
-
59
- def on_shutdown(connection)
60
- Async.logger.info(self) {"on_shutdown: #{connection}"}
61
- @connections.delete(connection)
62
-
63
- @connections.each do |connection|
64
- connection.send_message(message)
65
- end
66
- end
67
- end
68
-
69
- use RackUpgrade
70
-
71
- CHATTY = Chatty.new
72
-
73
- run lambda {|env|
74
- env['rack.upgrade'] = CHATTY
75
-
76
- return [200, {}, []]
77
- }
@@ -1,25 +0,0 @@
1
- # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require_relative 'upgrade'
22
-
23
- use Upgrade
24
-
25
- run lambda {|env| [404, {}, []]}