async-websocket 0.9.0 → 0.10.0

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.
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, {}, []]}