faye-websocket 0.10.6 → 0.11.1
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 +5 -5
- data/CHANGELOG.md +83 -51
- data/LICENSE.md +12 -0
- data/README.md +76 -72
- data/lib/faye/eventsource.rb +4 -4
- data/lib/faye/rack_stream.rb +1 -0
- data/lib/faye/websocket.rb +5 -4
- data/lib/faye/websocket/api.rb +13 -6
- data/lib/faye/websocket/api/event.rb +1 -1
- data/lib/faye/websocket/api/event_target.rb +2 -2
- data/lib/faye/websocket/client.rb +57 -34
- data/lib/faye/websocket/ssl_verifier.rb +89 -0
- metadata +28 -45
- data/examples/app.rb +0 -54
- data/examples/autobahn_client.rb +0 -47
- data/examples/client.rb +0 -34
- data/examples/config.ru +0 -13
- data/examples/haproxy.conf +0 -20
- data/examples/proxy_server.rb +0 -14
- data/examples/rainbows.conf +0 -3
- data/examples/server.rb +0 -50
- data/examples/sse.html +0 -38
- data/examples/ws.html +0 -43
data/lib/faye/eventsource.rb
CHANGED
@@ -63,7 +63,7 @@ module Faye
|
|
63
63
|
end
|
64
64
|
|
65
65
|
private
|
66
|
-
|
66
|
+
|
67
67
|
def open
|
68
68
|
return unless @ready_state == WebSocket::API::CONNECTING
|
69
69
|
|
@@ -83,9 +83,9 @@ module Faye
|
|
83
83
|
gsub(/(\r\n|\r|\n)/, '\1data: ')
|
84
84
|
|
85
85
|
frame = ""
|
86
|
-
frame << "event: #{options[:event]}\r\n" if options[:event]
|
87
|
-
frame << "id: #{options[:id]}\r\n" if options[:id]
|
88
|
-
frame << "data: #{message}\r\n\r\n"
|
86
|
+
frame << "event: #{ options[:event] }\r\n" if options[:event]
|
87
|
+
frame << "id: #{ options[:id] }\r\n" if options[:id]
|
88
|
+
frame << "data: #{ message }\r\n\r\n"
|
89
89
|
|
90
90
|
@stream.write(frame)
|
91
91
|
true
|
data/lib/faye/rack_stream.rb
CHANGED
data/lib/faye/websocket.rb
CHANGED
@@ -17,9 +17,10 @@ module Faye
|
|
17
17
|
class WebSocket
|
18
18
|
root = File.expand_path('../websocket', __FILE__)
|
19
19
|
|
20
|
-
autoload :Adapter,
|
21
|
-
autoload :API,
|
22
|
-
autoload :Client,
|
20
|
+
autoload :Adapter, root + '/adapter'
|
21
|
+
autoload :API, root + '/api'
|
22
|
+
autoload :Client, root + '/client'
|
23
|
+
autoload :SslVerifier, root + '/ssl_verifier'
|
23
24
|
|
24
25
|
ADAPTERS = {
|
25
26
|
'goliath' => :Goliath,
|
@@ -44,7 +45,7 @@ module Faye
|
|
44
45
|
def self.load_adapter(backend)
|
45
46
|
const = Kernel.const_get(ADAPTERS[backend]) rescue nil
|
46
47
|
require(backend) unless const
|
47
|
-
path = File.expand_path("../adapters/#{backend}.rb", __FILE__)
|
48
|
+
path = File.expand_path("../adapters/#{ backend }.rb", __FILE__)
|
48
49
|
require(path) if File.file?(path)
|
49
50
|
end
|
50
51
|
|
data/lib/faye/websocket/api.rb
CHANGED
@@ -43,7 +43,7 @@ module Faye
|
|
43
43
|
|
44
44
|
@driver.on(:open) { |e| open }
|
45
45
|
@driver.on(:message) { |e| receive_message(e.data) }
|
46
|
-
@driver.on(:close) { |e| begin_close(e.reason, e.code) }
|
46
|
+
@driver.on(:close) { |e| begin_close(e.reason, e.code, :wait_for_write => true) }
|
47
47
|
|
48
48
|
@driver.on(:error) do |error|
|
49
49
|
emit_error(error.message)
|
@@ -83,13 +83,16 @@ module Faye
|
|
83
83
|
unless code == 1000 or (code >= 3000 and code <= 4999)
|
84
84
|
raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
|
85
85
|
"The code must be either 1000, or between 3000 and 4999. " +
|
86
|
-
"#{code} is neither."
|
86
|
+
"#{ code } is neither."
|
87
|
+
end
|
88
|
+
|
89
|
+
if @ready_state < CLOSING
|
90
|
+
@close_timer = EventMachine.add_timer(CLOSE_TIMEOUT) { begin_close('', 1006) }
|
87
91
|
end
|
88
92
|
|
89
93
|
@ready_state = CLOSING unless @ready_state == CLOSED
|
90
|
-
@driver.close(reason, code)
|
91
94
|
|
92
|
-
@
|
95
|
+
@driver.close(reason, code)
|
93
96
|
end
|
94
97
|
|
95
98
|
def protocol
|
@@ -121,13 +124,17 @@ module Faye
|
|
121
124
|
dispatch_event(event)
|
122
125
|
end
|
123
126
|
|
124
|
-
def begin_close(reason, code)
|
127
|
+
def begin_close(reason, code, options = {})
|
125
128
|
return if @ready_state == CLOSED
|
126
129
|
@ready_state = CLOSING
|
127
130
|
@close_params = [reason, code]
|
128
131
|
|
129
132
|
if @stream
|
130
|
-
|
133
|
+
if options[:wait_for_write]
|
134
|
+
@stream.close_connection_after_writing
|
135
|
+
else
|
136
|
+
@stream.close_connection
|
137
|
+
end
|
131
138
|
else
|
132
139
|
finalize_close
|
133
140
|
end
|
@@ -10,7 +10,7 @@ module Faye::WebSocket::API
|
|
10
10
|
|
11
11
|
def initialize(event_type, options)
|
12
12
|
@type = event_type
|
13
|
-
options.each { |key, value| instance_variable_set("@#{key}", value) }
|
13
|
+
options.each { |key, value| instance_variable_set("@#{ key }", value) }
|
14
14
|
end
|
15
15
|
|
16
16
|
def init_event(event_type, can_bubble, cancelable)
|
@@ -5,10 +5,10 @@ module Faye::WebSocket::API
|
|
5
5
|
events = %w[open message error close]
|
6
6
|
|
7
7
|
events.each do |event_type|
|
8
|
-
define_method "on#{event_type}=" do |handler|
|
8
|
+
define_method "on#{ event_type }=" do |handler|
|
9
9
|
EventMachine.next_tick do
|
10
10
|
flush(event_type, handler)
|
11
|
-
instance_variable_set("@on#{event_type}", handler)
|
11
|
+
instance_variable_set("@on#{ event_type }", handler)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -7,7 +7,7 @@ module Faye
|
|
7
7
|
extend Forwardable
|
8
8
|
include API
|
9
9
|
|
10
|
-
DEFAULT_PORTS = {'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443}
|
10
|
+
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443 }
|
11
11
|
SECURE_PROTOCOLS = ['https', 'wss']
|
12
12
|
|
13
13
|
def_delegators :@driver, :headers, :status
|
@@ -17,58 +17,72 @@ module Faye
|
|
17
17
|
super(options) { ::WebSocket::Driver.client(self, :max_length => options[:max_length], :protocols => protocols) }
|
18
18
|
|
19
19
|
proxy = options.fetch(:proxy, {})
|
20
|
-
endpoint
|
21
|
-
port = endpoint.port || DEFAULT_PORTS[endpoint.scheme]
|
22
|
-
@secure = SECURE_PROTOCOLS.include?(endpoint.scheme)
|
20
|
+
@endpoint = URI.parse(proxy[:origin] || @url)
|
21
|
+
port = @endpoint.port || DEFAULT_PORTS[@endpoint.scheme]
|
23
22
|
@origin_tls = options.fetch(:tls, {})
|
24
23
|
@socket_tls = proxy[:origin] ? proxy.fetch(:tls, {}) : @origin_tls
|
25
24
|
|
26
|
-
|
27
|
-
@proxy = @driver.proxy(proxy[:origin])
|
28
|
-
if headers = proxy[:headers]
|
29
|
-
headers.each { |name, value| @proxy.set_header(name, value) }
|
30
|
-
end
|
25
|
+
configure_proxy(proxy)
|
31
26
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
27
|
+
EventMachine.connect(@endpoint.host, port, Connection) do |conn|
|
28
|
+
conn.parent = self
|
29
|
+
end
|
30
|
+
rescue => error
|
31
|
+
on_network_error(error)
|
32
|
+
end
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
private
|
35
|
+
|
36
|
+
def configure_proxy(proxy)
|
37
|
+
return unless proxy[:origin]
|
41
38
|
|
42
|
-
|
43
|
-
|
39
|
+
@proxy = @driver.proxy(proxy[:origin])
|
40
|
+
@proxy.on(:error) { |error| @driver.emit(:error, error) }
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
42
|
+
if headers = proxy[:headers]
|
43
|
+
headers.each { |name, value| @proxy.set_header(name, value) }
|
48
44
|
end
|
49
45
|
|
50
|
-
|
51
|
-
|
46
|
+
@proxy.on(:connect) do
|
47
|
+
@proxy = nil
|
48
|
+
start_tls(URI.parse(@url), @origin_tls)
|
49
|
+
@driver.start
|
52
50
|
end
|
53
|
-
rescue => error
|
54
|
-
emit_error("Network error: #{url}: #{error.message}")
|
55
|
-
finalize_close
|
56
51
|
end
|
57
52
|
|
58
|
-
|
53
|
+
def start_tls(uri, options)
|
54
|
+
return unless SECURE_PROTOCOLS.include?(uri.scheme)
|
55
|
+
|
56
|
+
tls_options = { :sni_hostname => uri.host, :verify_peer => true }.merge(options)
|
57
|
+
@ssl_verifier = SslVerifier.new(uri.host, tls_options)
|
58
|
+
@stream.start_tls(tls_options)
|
59
|
+
end
|
59
60
|
|
60
61
|
def on_connect(stream)
|
61
62
|
@stream = stream
|
62
|
-
|
63
|
-
if @secure
|
64
|
-
socket_tls = {:sni_hostname => URI.parse(@url).host}.merge(@socket_tls)
|
65
|
-
@stream.start_tls(socket_tls)
|
66
|
-
end
|
63
|
+
start_tls(@endpoint, @socket_tls)
|
67
64
|
|
68
65
|
worker = @proxy || @driver
|
69
66
|
worker.start
|
70
67
|
end
|
71
68
|
|
69
|
+
def on_network_error(error)
|
70
|
+
emit_error("Network error: #{ @url }: #{ error.message }")
|
71
|
+
finalize_close
|
72
|
+
end
|
73
|
+
|
74
|
+
def ssl_verify_peer(cert)
|
75
|
+
@ssl_verifier.ssl_verify_peer(cert)
|
76
|
+
rescue => error
|
77
|
+
on_network_error(error)
|
78
|
+
end
|
79
|
+
|
80
|
+
def ssl_handshake_completed
|
81
|
+
@ssl_verifier.ssl_handshake_completed
|
82
|
+
rescue => error
|
83
|
+
on_network_error(error)
|
84
|
+
end
|
85
|
+
|
72
86
|
module Connection
|
73
87
|
attr_accessor :parent
|
74
88
|
|
@@ -76,11 +90,20 @@ module Faye
|
|
76
90
|
parent.__send__(:on_connect, self)
|
77
91
|
end
|
78
92
|
|
93
|
+
def ssl_verify_peer(cert)
|
94
|
+
parent.__send__(:ssl_verify_peer, cert)
|
95
|
+
end
|
96
|
+
|
97
|
+
def ssl_handshake_completed
|
98
|
+
parent.__send__(:ssl_handshake_completed)
|
99
|
+
end
|
100
|
+
|
79
101
|
def receive_data(data)
|
80
102
|
parent.__send__(:parse, data)
|
81
103
|
end
|
82
104
|
|
83
|
-
def unbind
|
105
|
+
def unbind(error = nil)
|
106
|
+
parent.__send__(:emit_error, error) if error
|
84
107
|
parent.__send__(:finalize_close)
|
85
108
|
end
|
86
109
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# This code is based on the implementation in Faraday:
|
2
|
+
#
|
3
|
+
# https://github.com/lostisland/faraday/blob/v1.0.1/lib/faraday/adapter/em_http_ssl_patch.rb
|
4
|
+
#
|
5
|
+
# Faraday is published under the MIT license as detailed here:
|
6
|
+
#
|
7
|
+
# https://github.com/lostisland/faraday/blob/v1.0.1/LICENSE.md
|
8
|
+
#
|
9
|
+
# Copyright (c) 2009-2019 Rick Olson, Zack Hobson
|
10
|
+
#
|
11
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
12
|
+
# of this software and associated documentation files (the "Software"), to deal
|
13
|
+
# in the Software without restriction, including without limitation the rights
|
14
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
15
|
+
# copies of the Software, and to permit persons to whom the Software is
|
16
|
+
# furnished to do so, subject to the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be included in
|
19
|
+
# all copies or substantial portions of the Software.
|
20
|
+
|
21
|
+
require 'openssl'
|
22
|
+
|
23
|
+
module Faye
|
24
|
+
class WebSocket
|
25
|
+
|
26
|
+
SSLError = Class.new(OpenSSL::SSL::SSLError)
|
27
|
+
|
28
|
+
class SslVerifier
|
29
|
+
def initialize(hostname, ssl_opts)
|
30
|
+
@hostname = hostname
|
31
|
+
@ssl_opts = ssl_opts
|
32
|
+
@cert_store = OpenSSL::X509::Store.new
|
33
|
+
|
34
|
+
if root = @ssl_opts[:root_cert_file]
|
35
|
+
[root].flatten.each { |ca_path| @cert_store.add_file(ca_path) }
|
36
|
+
else
|
37
|
+
@cert_store.set_default_paths
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def ssl_verify_peer(cert_text)
|
42
|
+
return true unless should_verify?
|
43
|
+
|
44
|
+
certificate = parse_cert(cert_text)
|
45
|
+
return false unless certificate
|
46
|
+
|
47
|
+
unless @cert_store.verify(certificate)
|
48
|
+
raise SSLError, "Unable to verify the server certificate for '#{ @hostname }'"
|
49
|
+
end
|
50
|
+
|
51
|
+
store_cert(certificate)
|
52
|
+
@last_cert = certificate
|
53
|
+
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def ssl_handshake_completed
|
58
|
+
return unless should_verify?
|
59
|
+
|
60
|
+
unless identity_verified?
|
61
|
+
raise SSLError, "Host '#{ @hostname }' does not match the server certificate"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def should_verify?
|
68
|
+
@ssl_opts[:verify_peer] != false
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_cert(cert_text)
|
72
|
+
OpenSSL::X509::Certificate.new(cert_text)
|
73
|
+
rescue OpenSSL::X509::CertificateError
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def store_cert(certificate)
|
78
|
+
@cert_store.add_cert(certificate)
|
79
|
+
rescue OpenSSL::X509::StoreError => error
|
80
|
+
raise error unless error.message == 'cert already in hash table'
|
81
|
+
end
|
82
|
+
|
83
|
+
def identity_verified?
|
84
|
+
@last_cert and OpenSSL::SSL.verify_certificate_identity(@last_cert, @hostname)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faye-websocket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Coglan
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: public_suffix
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "<"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: 1.5.0
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "<"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 1.5.0
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: puma
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,45 +123,51 @@ dependencies:
|
|
137
123
|
- !ruby/object:Gem::Version
|
138
124
|
version: 0.2.0
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
126
|
+
name: thin
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
|
-
- - "
|
129
|
+
- - ">="
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
131
|
+
version: 1.2.0
|
132
|
+
- - ">"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
146
135
|
type: :development
|
147
136
|
prerelease: false
|
148
137
|
version_requirements: !ruby/object:Gem::Requirement
|
149
138
|
requirements:
|
150
|
-
- - "
|
139
|
+
- - ">="
|
151
140
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
141
|
+
version: 1.2.0
|
142
|
+
- - ">"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
153
145
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
146
|
+
name: rainbows
|
155
147
|
requirement: !ruby/object:Gem::Requirement
|
156
148
|
requirements:
|
157
|
-
- - "
|
149
|
+
- - "~>"
|
158
150
|
- !ruby/object:Gem::Version
|
159
|
-
version:
|
151
|
+
version: 4.4.0
|
160
152
|
type: :development
|
161
153
|
prerelease: false
|
162
154
|
version_requirements: !ruby/object:Gem::Requirement
|
163
155
|
requirements:
|
164
|
-
- - "
|
156
|
+
- - "~>"
|
165
157
|
- !ruby/object:Gem::Version
|
166
|
-
version:
|
158
|
+
version: 4.4.0
|
167
159
|
- !ruby/object:Gem::Dependency
|
168
160
|
name: goliath
|
169
161
|
requirement: !ruby/object:Gem::Requirement
|
170
162
|
requirements:
|
171
|
-
- - "
|
163
|
+
- - ">"
|
172
164
|
- !ruby/object:Gem::Version
|
173
165
|
version: '0'
|
174
166
|
type: :development
|
175
167
|
prerelease: false
|
176
168
|
version_requirements: !ruby/object:Gem::Requirement
|
177
169
|
requirements:
|
178
|
-
- - "
|
170
|
+
- - ">"
|
179
171
|
- !ruby/object:Gem::Version
|
180
172
|
version: '0'
|
181
173
|
- !ruby/object:Gem::Dependency
|
@@ -192,7 +184,7 @@ dependencies:
|
|
192
184
|
- - ">="
|
193
185
|
- !ruby/object:Gem::Version
|
194
186
|
version: 4.0.0
|
195
|
-
description:
|
187
|
+
description:
|
196
188
|
email: jcoglan@gmail.com
|
197
189
|
executables: []
|
198
190
|
extensions: []
|
@@ -200,17 +192,8 @@ extra_rdoc_files:
|
|
200
192
|
- README.md
|
201
193
|
files:
|
202
194
|
- CHANGELOG.md
|
195
|
+
- LICENSE.md
|
203
196
|
- README.md
|
204
|
-
- examples/app.rb
|
205
|
-
- examples/autobahn_client.rb
|
206
|
-
- examples/client.rb
|
207
|
-
- examples/config.ru
|
208
|
-
- examples/haproxy.conf
|
209
|
-
- examples/proxy_server.rb
|
210
|
-
- examples/rainbows.conf
|
211
|
-
- examples/server.rb
|
212
|
-
- examples/sse.html
|
213
|
-
- examples/ws.html
|
214
197
|
- lib/faye/adapters/goliath.rb
|
215
198
|
- lib/faye/adapters/rainbows.rb
|
216
199
|
- lib/faye/adapters/rainbows_client.rb
|
@@ -223,11 +206,12 @@ files:
|
|
223
206
|
- lib/faye/websocket/api/event.rb
|
224
207
|
- lib/faye/websocket/api/event_target.rb
|
225
208
|
- lib/faye/websocket/client.rb
|
209
|
+
- lib/faye/websocket/ssl_verifier.rb
|
226
210
|
homepage: https://github.com/faye/faye-websocket-ruby
|
227
211
|
licenses:
|
228
|
-
-
|
212
|
+
- Apache-2.0
|
229
213
|
metadata: {}
|
230
|
-
post_install_message:
|
214
|
+
post_install_message:
|
231
215
|
rdoc_options:
|
232
216
|
- "--main"
|
233
217
|
- README.md
|
@@ -246,9 +230,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
246
230
|
- !ruby/object:Gem::Version
|
247
231
|
version: '0'
|
248
232
|
requirements: []
|
249
|
-
|
250
|
-
|
251
|
-
signing_key:
|
233
|
+
rubygems_version: 3.1.6
|
234
|
+
signing_key:
|
252
235
|
specification_version: 4
|
253
236
|
summary: Standards-compliant WebSocket server and client
|
254
237
|
test_files: []
|