whoosh 1.3.2 → 1.4.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 +4 -4
- data/README.md +1 -1
- data/lib/whoosh/streaming/websocket.rb +26 -126
- 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: cf87392b23cb5b6a3cdc3fc27c06df4d62d7dd3b780e8695f9479cf869a0922a
|
|
4
|
+
data.tar.gz: 249803c4606645c7032deae70836954c3f1255ed5c9bf44342d07cf50afb2214
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c871107c868e72d7f27596a48719dd752155d91ff114937b7448a7474e1a0ac6bdfa39dec8db23fe857e9728e3e2a0db3517013a70f820590386ebc7876b2707
|
|
7
|
+
data.tar.gz: 6ebad6447841c6587c0b3853fdb4f2666680db1e6e1eaf01a1fb1edb854af0a13ebd5e5a205b5866686346caf747a0aa49cbba30b2587afec681dc409829de23
|
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 "
|
|
5
|
-
require "base64"
|
|
4
|
+
require "faye/websocket"
|
|
6
5
|
|
|
7
6
|
module Whoosh
|
|
8
7
|
module Streaming
|
|
9
8
|
class WebSocket
|
|
10
|
-
GUID = "258EAFA5-E914-47DA-95CA-5AB5DC65C3E5"
|
|
11
|
-
|
|
12
9
|
attr_reader :env
|
|
13
10
|
|
|
14
11
|
def initialize(env)
|
|
15
12
|
@env = env
|
|
16
|
-
@
|
|
13
|
+
@ws = nil
|
|
17
14
|
@closed = false
|
|
15
|
+
@on_open = nil
|
|
18
16
|
@on_message = nil
|
|
19
17
|
@on_close = nil
|
|
20
|
-
@on_open = nil
|
|
21
18
|
end
|
|
22
19
|
|
|
23
20
|
# Check if the request is a WebSocket upgrade
|
|
24
21
|
def self.websocket?(env)
|
|
25
|
-
env
|
|
26
|
-
env["HTTP_CONNECTION"]&.downcase&.include?("upgrade")
|
|
22
|
+
Faye::WebSocket.websocket?(env)
|
|
27
23
|
end
|
|
28
24
|
|
|
29
25
|
# Register callbacks
|
|
@@ -41,154 +37,58 @@ 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
|
-
@io&.close rescue nil
|
|
54
|
-
@on_close&.call
|
|
48
|
+
@ws&.close(code || 1000, reason || "")
|
|
55
49
|
end
|
|
56
50
|
|
|
57
51
|
def closed?
|
|
58
52
|
@closed
|
|
59
53
|
end
|
|
60
54
|
|
|
61
|
-
# Returns a Rack response
|
|
55
|
+
# Returns a Rack response — hijacks the connection via faye-websocket
|
|
62
56
|
def rack_response
|
|
63
57
|
unless self.class.websocket?(@env)
|
|
64
58
|
return [400, { "content-type" => "text/plain" }, ["Not a WebSocket request"]]
|
|
65
59
|
end
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
key = @env["HTTP_SEC_WEBSOCKET_KEY"]
|
|
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
|
-
}
|
|
61
|
+
@ws = Faye::WebSocket.new(@env)
|
|
76
62
|
|
|
77
|
-
|
|
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
|
|
63
|
+
@ws.on :open do |_event|
|
|
89
64
|
@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, {}, []]
|
|
96
|
-
else
|
|
97
|
-
# Fallback: return 101 and let the server handle hijack
|
|
98
|
-
[101, headers, []]
|
|
99
65
|
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# For testing — simulate without real socket
|
|
103
|
-
def trigger_message(msg)
|
|
104
|
-
@on_message&.call(msg)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def trigger_close
|
|
108
|
-
@on_close&.call
|
|
109
|
-
@closed = true
|
|
110
|
-
end
|
|
111
66
|
|
|
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
|
|
67
|
+
@ws.on :message do |event|
|
|
68
|
+
@on_message&.call(event.data)
|
|
128
69
|
end
|
|
129
|
-
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
|
130
|
-
@closed = true
|
|
131
|
-
@on_close&.call
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def read_frame
|
|
135
|
-
return nil unless @io && !@io.closed?
|
|
136
|
-
|
|
137
|
-
first_byte = @io.readbyte
|
|
138
|
-
fin = (first_byte & 0x80) != 0
|
|
139
|
-
opcode = first_byte & 0x0F
|
|
140
|
-
|
|
141
|
-
second_byte = @io.readbyte
|
|
142
|
-
masked = (second_byte & 0x80) != 0
|
|
143
|
-
length = second_byte & 0x7F
|
|
144
|
-
|
|
145
|
-
if length == 126
|
|
146
|
-
length = @io.read(2).unpack1("n")
|
|
147
|
-
elsif length == 127
|
|
148
|
-
length = @io.read(8).unpack1("Q>")
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
mask_key = masked ? @io.read(4).bytes : nil
|
|
152
|
-
payload = @io.read(length)&.bytes || []
|
|
153
70
|
|
|
154
|
-
|
|
155
|
-
|
|
71
|
+
@ws.on :close do |event|
|
|
72
|
+
@closed = true
|
|
73
|
+
@on_close&.call(event.code, event.reason)
|
|
74
|
+
@ws = nil
|
|
156
75
|
end
|
|
157
76
|
|
|
158
|
-
|
|
159
|
-
rescue EOFError, IOError
|
|
160
|
-
nil
|
|
77
|
+
@ws.rack_response
|
|
161
78
|
end
|
|
162
79
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
bytes = data.encode("UTF-8").bytes
|
|
167
|
-
frame = [0x80 | opcode] # FIN + opcode
|
|
168
|
-
|
|
169
|
-
if bytes.length < 126
|
|
170
|
-
frame << bytes.length
|
|
171
|
-
elsif bytes.length < 65536
|
|
172
|
-
frame << 126
|
|
173
|
-
frame += [bytes.length].pack("n").bytes
|
|
174
|
-
else
|
|
175
|
-
frame << 127
|
|
176
|
-
frame += [bytes.length].pack("Q>").bytes
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
frame += bytes
|
|
180
|
-
@io.write(frame.pack("C*"))
|
|
181
|
-
@io.flush
|
|
182
|
-
rescue IOError, Errno::EPIPE
|
|
183
|
-
@closed = true
|
|
80
|
+
# For testing without real socket
|
|
81
|
+
def trigger_message(msg)
|
|
82
|
+
@on_message&.call(msg)
|
|
184
83
|
end
|
|
185
84
|
|
|
186
|
-
def
|
|
187
|
-
|
|
85
|
+
def trigger_close(code = 1000, reason = "")
|
|
86
|
+
@on_close&.call(code, reason)
|
|
87
|
+
@closed = true
|
|
188
88
|
end
|
|
189
89
|
|
|
190
|
-
def
|
|
191
|
-
|
|
90
|
+
def trigger_open
|
|
91
|
+
@on_open&.call
|
|
192
92
|
end
|
|
193
93
|
end
|
|
194
94
|
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.0
|
|
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
|