whoosh 1.3.2 → 1.4.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 +4 -4
- data/README.md +1 -1
- data/lib/whoosh/streaming/websocket.rb +72 -109
- data/lib/whoosh/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5baff744454d485e7f9788aca18285ece0c8901a5abea6f9c2f3fd4a22a7de75
|
|
4
|
+
data.tar.gz: 6c2062d73b6bec5e60c0adfd897e7e8aec5d10fa366439471592fc196bfe5bbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ef32b36e7405fe117eefc60908baf71a9553d328ccd2fd7823c715b7e37d4f01aeedc29a7cb1709e865410d407a32bc511f49353b90339702a06bb7f9a31425
|
|
7
|
+
data.tar.gz: 75188ded90b9205cb7c779d18252ef842989ec84d76ad78e1167d4ca267e544918b39c5d2a3596abe0b2d6001620e98e4272a75bdd62491b4130127bba78487c
|
data/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<img src="https://img.shields.io/badge/ruby-%3E%3D%203.4.0-red" alt="Ruby">
|
|
14
14
|
<img src="https://img.shields.io/badge/rack-3.0-blue" alt="Rack">
|
|
15
15
|
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
|
16
|
-
<img src="https://img.shields.io/badge/tests-
|
|
16
|
+
<img src="https://img.shields.io/badge/tests-542%20passing-brightgreen" alt="Tests">
|
|
17
17
|
<img src="https://img.shields.io/badge/overhead-2.5%C2%B5s-orange" alt="Performance">
|
|
18
18
|
</p>
|
|
19
19
|
|
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
-
require "digest"
|
|
5
|
-
require "base64"
|
|
6
4
|
|
|
7
5
|
module Whoosh
|
|
8
6
|
module Streaming
|
|
9
7
|
class WebSocket
|
|
10
|
-
GUID = "258EAFA5-E914-47DA-95CA-5AB5DC65C3E5"
|
|
11
|
-
|
|
12
8
|
attr_reader :env
|
|
13
9
|
|
|
14
10
|
def initialize(env)
|
|
15
11
|
@env = env
|
|
16
|
-
@
|
|
12
|
+
@ws = nil
|
|
17
13
|
@closed = false
|
|
14
|
+
@on_open = nil
|
|
18
15
|
@on_message = nil
|
|
19
16
|
@on_close = nil
|
|
20
|
-
@on_open = nil
|
|
21
17
|
end
|
|
22
18
|
|
|
23
19
|
# Check if the request is a WebSocket upgrade
|
|
24
20
|
def self.websocket?(env)
|
|
25
|
-
env["HTTP_UPGRADE"]
|
|
26
|
-
|
|
21
|
+
upgrade = env["HTTP_UPGRADE"]
|
|
22
|
+
upgrade && upgrade.downcase == "websocket"
|
|
27
23
|
end
|
|
28
24
|
|
|
29
25
|
# Register callbacks
|
|
@@ -41,154 +37,121 @@ module Whoosh
|
|
|
41
37
|
|
|
42
38
|
# Send data to the client
|
|
43
39
|
def send(data)
|
|
44
|
-
return if @closed
|
|
40
|
+
return if @closed || @ws.nil?
|
|
45
41
|
formatted = data.is_a?(String) ? data : JSON.generate(data)
|
|
46
|
-
|
|
42
|
+
@ws.send(formatted)
|
|
47
43
|
end
|
|
48
44
|
|
|
49
|
-
def close
|
|
45
|
+
def close(code = nil, reason = nil)
|
|
50
46
|
return if @closed
|
|
51
47
|
@closed = true
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
@ws&.close(code || 1000, reason || "")
|
|
49
|
+
rescue
|
|
50
|
+
# Already closed
|
|
55
51
|
end
|
|
56
52
|
|
|
57
53
|
def closed?
|
|
58
54
|
@closed
|
|
59
55
|
end
|
|
60
56
|
|
|
61
|
-
# Returns a Rack response
|
|
57
|
+
# Returns a Rack response — auto-detects Faye (Puma) or Async (Falcon)
|
|
62
58
|
def rack_response
|
|
63
59
|
unless self.class.websocket?(@env)
|
|
64
60
|
return [400, { "content-type" => "text/plain" }, ["Not a WebSocket request"]]
|
|
65
61
|
end
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
accept = Base64.strict_encode64(Digest::SHA1.digest(key + GUID))
|
|
70
|
-
|
|
71
|
-
headers = {
|
|
72
|
-
"Upgrade" => "websocket",
|
|
73
|
-
"Connection" => "Upgrade",
|
|
74
|
-
"Sec-WebSocket-Accept" => accept
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# Use rack.hijack to take over the connection
|
|
78
|
-
if @env["rack.hijack"]
|
|
79
|
-
@env["rack.hijack"].call
|
|
80
|
-
@io = @env["rack.hijack_io"]
|
|
81
|
-
|
|
82
|
-
# Send handshake response manually
|
|
83
|
-
@io.write("HTTP/1.1 101 Switching Protocols\r\n")
|
|
84
|
-
headers.each { |k, v| @io.write("#{k}: #{v}\r\n") }
|
|
85
|
-
@io.write("\r\n")
|
|
86
|
-
@io.flush
|
|
87
|
-
|
|
88
|
-
# Notify open
|
|
89
|
-
@on_open&.call
|
|
90
|
-
|
|
91
|
-
# Start reading frames in a thread
|
|
92
|
-
Thread.new { read_loop }
|
|
93
|
-
|
|
94
|
-
# Return a dummy response (connection is hijacked)
|
|
95
|
-
[-1, {}, []]
|
|
63
|
+
if async_websocket_available? && falcon_env?
|
|
64
|
+
rack_response_async
|
|
96
65
|
else
|
|
97
|
-
|
|
98
|
-
[101, headers, []]
|
|
66
|
+
rack_response_faye
|
|
99
67
|
end
|
|
100
68
|
end
|
|
101
69
|
|
|
102
|
-
# For testing
|
|
70
|
+
# For testing without real socket
|
|
103
71
|
def trigger_message(msg)
|
|
104
72
|
@on_message&.call(msg)
|
|
105
73
|
end
|
|
106
74
|
|
|
107
|
-
def trigger_close
|
|
108
|
-
@on_close&.call
|
|
75
|
+
def trigger_close(code = 1000, reason = "")
|
|
76
|
+
@on_close&.call(code, reason)
|
|
109
77
|
@closed = true
|
|
110
78
|
end
|
|
111
79
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def read_loop
|
|
115
|
-
while !@closed && @io && !@io.closed?
|
|
116
|
-
frame = read_frame
|
|
117
|
-
break unless frame
|
|
118
|
-
|
|
119
|
-
case frame[:opcode]
|
|
120
|
-
when 0x1 # Text frame
|
|
121
|
-
@on_message&.call(frame[:data])
|
|
122
|
-
when 0x8 # Close frame
|
|
123
|
-
close
|
|
124
|
-
break
|
|
125
|
-
when 0x9 # Ping
|
|
126
|
-
write_pong(frame[:data])
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
|
130
|
-
@closed = true
|
|
131
|
-
@on_close&.call
|
|
80
|
+
def trigger_open
|
|
81
|
+
@on_open&.call
|
|
132
82
|
end
|
|
133
83
|
|
|
134
|
-
|
|
135
|
-
return nil unless @io && !@io.closed?
|
|
84
|
+
private
|
|
136
85
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
86
|
+
# Faye WebSocket — works with Puma (threads + EventMachine)
|
|
87
|
+
def rack_response_faye
|
|
88
|
+
require "faye/websocket"
|
|
140
89
|
|
|
141
|
-
|
|
142
|
-
masked = (second_byte & 0x80) != 0
|
|
143
|
-
length = second_byte & 0x7F
|
|
90
|
+
@ws = Faye::WebSocket.new(@env)
|
|
144
91
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
elsif length == 127
|
|
148
|
-
length = @io.read(8).unpack1("Q>")
|
|
92
|
+
@ws.on :open do |_event|
|
|
93
|
+
@on_open&.call
|
|
149
94
|
end
|
|
150
95
|
|
|
151
|
-
|
|
152
|
-
|
|
96
|
+
@ws.on :message do |event|
|
|
97
|
+
@on_message&.call(event.data)
|
|
98
|
+
end
|
|
153
99
|
|
|
154
|
-
|
|
155
|
-
|
|
100
|
+
@ws.on :close do |event|
|
|
101
|
+
@closed = true
|
|
102
|
+
@on_close&.call(event.code, event.reason)
|
|
103
|
+
@ws = nil
|
|
156
104
|
end
|
|
157
105
|
|
|
158
|
-
|
|
159
|
-
rescue EOFError, IOError
|
|
160
|
-
nil
|
|
106
|
+
@ws.rack_response
|
|
161
107
|
end
|
|
162
108
|
|
|
163
|
-
|
|
164
|
-
|
|
109
|
+
# Async WebSocket — works with Falcon (fibers + async)
|
|
110
|
+
def rack_response_async
|
|
111
|
+
require "async/websocket/adapters/rack"
|
|
165
112
|
|
|
166
|
-
|
|
167
|
-
|
|
113
|
+
Async::WebSocket::Adapters::Rack.open(@env, protocols: ["ws"]) do |connection|
|
|
114
|
+
@ws = AsyncWSWrapper.new(connection)
|
|
115
|
+
@on_open&.call
|
|
168
116
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
117
|
+
while (message = connection.read)
|
|
118
|
+
@on_message&.call(message.to_str)
|
|
119
|
+
end
|
|
120
|
+
rescue EOFError, Protocol::WebSocket::ClosedError
|
|
121
|
+
# Client disconnected
|
|
122
|
+
ensure
|
|
123
|
+
@closed = true
|
|
124
|
+
@on_close&.call(1000, "")
|
|
125
|
+
@ws = nil
|
|
177
126
|
end
|
|
127
|
+
end
|
|
178
128
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
@
|
|
182
|
-
rescue IOError, Errno::EPIPE
|
|
183
|
-
@closed = true
|
|
129
|
+
def falcon_env?
|
|
130
|
+
# Falcon sets async.* keys in the env
|
|
131
|
+
@env.key?("async.reactor") || @env.key?("protocol.http.request")
|
|
184
132
|
end
|
|
185
133
|
|
|
186
|
-
def
|
|
187
|
-
|
|
134
|
+
def async_websocket_available?
|
|
135
|
+
require "async/websocket/adapters/rack"
|
|
136
|
+
true
|
|
137
|
+
rescue LoadError
|
|
138
|
+
false
|
|
188
139
|
end
|
|
189
140
|
|
|
190
|
-
|
|
191
|
-
|
|
141
|
+
# Wrapper to give async-websocket the same #send interface
|
|
142
|
+
class AsyncWSWrapper
|
|
143
|
+
def initialize(connection)
|
|
144
|
+
@connection = connection
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def send(data)
|
|
148
|
+
@connection.write(Protocol::WebSocket::TextMessage.generate(data))
|
|
149
|
+
@connection.flush
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def close(code = 1000, reason = "")
|
|
153
|
+
@connection.close
|
|
154
|
+
end
|
|
192
155
|
end
|
|
193
156
|
end
|
|
194
157
|
end
|
data/lib/whoosh/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: whoosh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Johannes Dwi Cahyo
|
|
@@ -107,6 +107,20 @@ dependencies:
|
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: '1.8'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: faye-websocket
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.11'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.11'
|
|
110
124
|
- !ruby/object:Gem::Dependency
|
|
111
125
|
name: rspec
|
|
112
126
|
requirement: !ruby/object:Gem::Requirement
|