monkrb 0.21.1 → 0.22.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/CHANGELOG.md +94 -0
- data/README.md +4 -4
- data/lib/monk/auth/errors.rb +5 -0
- data/lib/monk/auth/sweep.rb +23 -0
- data/lib/monk/auth.rb +101 -6
- data/lib/monk/bin.rb +94 -0
- data/lib/monk/generators/auth.rb +1 -0
- data/lib/monk/generators/jobs.rb +3 -1
- data/lib/monk/jobs/adapters/pg.rb +90 -2
- data/lib/monk/jobs/cron.rb +180 -0
- data/lib/monk/jobs/errors.rb +21 -0
- data/lib/monk/jobs/firing.rb +11 -0
- data/lib/monk/jobs/recurring_task.rb +9 -0
- data/lib/monk/jobs/runtime.rb +38 -2
- data/lib/monk/jobs/scheduler.rb +91 -0
- data/lib/monk/jobs.rb +87 -8
- data/lib/monk/live/session.rb +16 -9
- data/lib/monk/templates/auth/agents.md +10 -3
- data/lib/monk/templates/auth/app/routes/auth.rb +4 -8
- data/lib/monk/templates/auth/config/auth.rb +1 -0
- data/lib/monk/templates/auth/db/migrate/00000000000004_create_login_requests.down.sql +2 -0
- data/lib/monk/templates/auth/db/migrate/00000000000004_create_login_requests.up.sql +16 -0
- data/lib/monk/templates/auth/setup.md +16 -3
- data/lib/monk/templates/auth/test/auth_test.rb +6 -0
- data/lib/monk/templates/base/Gemfile +1 -1
- data/lib/monk/templates/base/config/load.rb +5 -0
- data/lib/monk/templates/jobs/agents.md +15 -4
- data/lib/monk/templates/jobs/config/recurring.rb +21 -0
- data/lib/monk/templates/jobs/db/migrate/00000000000003_create_recurring_tables.down.sql +1 -0
- data/lib/monk/templates/jobs/db/migrate/00000000000003_create_recurring_tables.up.sql +13 -0
- data/lib/monk/templates/jobs/setup.md +20 -2
- data/lib/monk/templates/websocket/bin/websocket_server +14 -0
- data/lib/monk/templates/websocket/setup.md +2 -1
- data/lib/monk/version.rb +1 -1
- data/lib/monk/websocket/handshake.rb +32 -9
- data/lib/monk/websocket/pg_fanout.rb +16 -7
- data/lib/monk/websocket/redis_fanout.rb +16 -7
- data/lib/monk/websocket/registry.rb +40 -5
- data/lib/monk/websocket/server.rb +211 -47
- metadata +11 -1
|
@@ -55,14 +55,49 @@ module Monk
|
|
|
55
55
|
# hostile client on a public endpoint, not an opt-in behavior
|
|
56
56
|
# change, so a server that doesn't mention it still gets the
|
|
57
57
|
# default cap rather than no cap at all.
|
|
58
|
+
#
|
|
59
|
+
# max_handshakes: caps the connections still in their handshake (the
|
|
60
|
+
# HTTP upgrade and, with authenticate:, verifying the session). At the
|
|
61
|
+
# cap the accept loop stops accepting until one finishes, and new
|
|
62
|
+
# connects wait in the kernel's listen backlog. Without it, a burst
|
|
63
|
+
# past what the process can verify (a restart's reconnect wave) starts
|
|
64
|
+
# every handshake at once, fills db_pool:'s queue, and ends with most
|
|
65
|
+
# of them timing out instead of the rest queueing
|
|
66
|
+
# (docs/plan-ws-connect-ceiling.md, item 1). Kept well under the
|
|
67
|
+
# pool's queue (1,000 by default), so a full cap can't overfill it.
|
|
68
|
+
# Always on, like max_payload_size:.
|
|
69
|
+
#
|
|
70
|
+
# handshake_timeout: (seconds) bounds the whole handshake, from
|
|
71
|
+
# accept to the 101, and Handshake::MAX_REQUEST_SIZE its request; a
|
|
72
|
+
# client past the deadline is dropped, one past the size gets a 431.
|
|
73
|
+
# Without them, a socket that connects and sends nothing holds a
|
|
74
|
+
# max_handshakes: slot forever, so a few hundred of them would shut
|
|
75
|
+
# the server, and a request that never ends grows without limit
|
|
76
|
+
# (item 2). Off once the 101 is written: an idle socket is the
|
|
77
|
+
# handler's business, not the handshake's.
|
|
78
|
+
#
|
|
79
|
+
# reuse_port: true lets several processes listen on the one port, and
|
|
80
|
+
# on Linux the kernel spreads connects across them. Each has its own
|
|
81
|
+
# heap, so its own GC pauses, over a share of the sockets: on Ruby 4.0
|
|
82
|
+
# K processes cut what GC costs a connect by about K squared
|
|
83
|
+
# (item 8). A broadcast reaches all of them through the fan-out.
|
|
58
84
|
AUTHENTICATE_MODES = [false, true, :optional].freeze
|
|
59
85
|
|
|
86
|
+
DEFAULT_MAX_HANDSHAKES = 128
|
|
87
|
+
DEFAULT_HANDSHAKE_TIMEOUT = 10
|
|
88
|
+
|
|
89
|
+
# What the handshake's verify returns when it raised instead of
|
|
90
|
+
# answering (the pool full or timed out, the database down): neither
|
|
91
|
+
# a subject nor "no session", so the connection gets a 503.
|
|
92
|
+
VERIFY_FAILED = Object.new.freeze
|
|
93
|
+
|
|
60
94
|
# The signals #run treats as "stop": Ctrl+C and TERM.
|
|
61
95
|
STOP_SIGNALS = [Signal.list.fetch("INT"), Signal.list.fetch("TERM")].freeze
|
|
62
96
|
|
|
63
97
|
def initialize(
|
|
64
98
|
port:, bind: "0.0.0.0", allowed_origins: nil, authenticate: false, ping_interval: nil,
|
|
65
|
-
reverify_interval: nil, max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE, db_pool: nil
|
|
99
|
+
reverify_interval: nil, max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE, db_pool: nil,
|
|
100
|
+
max_handshakes: DEFAULT_MAX_HANDSHAKES, handshake_timeout: DEFAULT_HANDSHAKE_TIMEOUT, reuse_port: false
|
|
66
101
|
)
|
|
67
102
|
if ping_interval && !ping_interval.positive?
|
|
68
103
|
raise ArgumentError, "ping_interval must be positive, got #{ping_interval.inspect}"
|
|
@@ -85,6 +120,14 @@ module Monk
|
|
|
85
120
|
raise ArgumentError, "max_payload_size must be positive, got #{max_payload_size.inspect}"
|
|
86
121
|
end
|
|
87
122
|
|
|
123
|
+
unless max_handshakes.is_a?(Integer) && max_handshakes.positive?
|
|
124
|
+
raise ArgumentError, "max_handshakes must be a positive Integer, got #{max_handshakes.inspect}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless handshake_timeout.is_a?(Numeric) && handshake_timeout.positive?
|
|
128
|
+
raise ArgumentError, "handshake_timeout must be positive, got #{handshake_timeout.inspect}"
|
|
129
|
+
end
|
|
130
|
+
|
|
88
131
|
if authenticate
|
|
89
132
|
unless defined?(Monk::Auth) && Monk::Auth.config
|
|
90
133
|
raise Monk::AuthNotConfiguredError,
|
|
@@ -106,19 +149,46 @@ module Monk
|
|
|
106
149
|
# declared or isn't started in this process.
|
|
107
150
|
Monk::Persistence::Pg.pool(db_pool) if db_pool
|
|
108
151
|
|
|
109
|
-
@tcp_server = TCPServer.new(bind, port)
|
|
152
|
+
@tcp_server = reuse_port ? Server.listen_reusing_port(bind, port) : TCPServer.new(bind, port)
|
|
110
153
|
@allowed_origins = allowed_origins ? Ractor.make_shareable(allowed_origins.dup) : nil
|
|
111
154
|
@authenticate = authenticate
|
|
112
155
|
@ping_interval = ping_interval
|
|
113
156
|
@reverify_interval = reverify_interval
|
|
114
157
|
@max_payload_size = max_payload_size
|
|
115
158
|
@db_pool = db_pool
|
|
159
|
+
@max_handshakes = max_handshakes
|
|
160
|
+
@handshake_timeout = handshake_timeout
|
|
116
161
|
end
|
|
117
162
|
|
|
118
163
|
def port
|
|
119
164
|
@tcp_server.addr[1]
|
|
120
165
|
end
|
|
121
166
|
|
|
167
|
+
# reuse_port:, for several processes on one port (bin/websocket_server's
|
|
168
|
+
# WS_PROCESSES): SO_REUSEPORT has to be set before the bind, which
|
|
169
|
+
# TCPServer.new does at once. On Linux the kernel spreads connects
|
|
170
|
+
# across the processes listening.
|
|
171
|
+
def self.listen_reusing_port(bind, port)
|
|
172
|
+
unless defined?(Socket::SO_REUSEPORT)
|
|
173
|
+
raise ArgumentError, "reuse_port: true needs SO_REUSEPORT, which this platform doesn't have"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
address = Addrinfo.tcp(bind, port)
|
|
177
|
+
socket = Socket.new(address.afamily, :STREAM)
|
|
178
|
+
socket.setsockopt(:SOCKET, :REUSEADDR, true)
|
|
179
|
+
socket.setsockopt(:SOCKET, :REUSEPORT, true)
|
|
180
|
+
socket.bind(address)
|
|
181
|
+
socket.listen(Socket::SOMAXCONN)
|
|
182
|
+
# A TCPServer on the same descriptor, so accept returns a TCPSocket
|
|
183
|
+
# as without reuse_port:.
|
|
184
|
+
socket.autoclose = false
|
|
185
|
+
TCPServer.for_fd(socket.fileno)
|
|
186
|
+
rescue StandardError
|
|
187
|
+
socket&.autoclose = true
|
|
188
|
+
socket&.close
|
|
189
|
+
raise
|
|
190
|
+
end
|
|
191
|
+
|
|
122
192
|
def run(&block)
|
|
123
193
|
shareable_block =
|
|
124
194
|
begin
|
|
@@ -130,16 +200,30 @@ module Monk
|
|
|
130
200
|
"top-level block -- mirrors StateRactor#update's own constraint)"
|
|
131
201
|
end
|
|
132
202
|
|
|
203
|
+
# One slot a handshake in flight (max_handshakes:). Each connection
|
|
204
|
+
# Ractor sends to handshake_done once its handshake is over, however
|
|
205
|
+
# it ended, and the releaser thread frees a slot for it.
|
|
206
|
+
slots = Thread::SizedQueue.new(@max_handshakes)
|
|
207
|
+
handshake_done = Ractor::Port.new
|
|
208
|
+
releaser = Thread.new do
|
|
209
|
+
loop do
|
|
210
|
+
handshake_done.receive
|
|
211
|
+
slots.pop
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
133
215
|
loop do
|
|
216
|
+
slots.push(true)
|
|
134
217
|
socket = @tcp_server.accept
|
|
135
218
|
ractor = Ractor.new(
|
|
136
219
|
shareable_block, @authenticate, @allowed_origins, @ping_interval, @reverify_interval, @max_payload_size,
|
|
137
|
-
@db_pool
|
|
138
|
-
) do |blk, authenticate, origins, ping_interval, reverify_interval, max_payload_size, db_pool|
|
|
220
|
+
@db_pool, handshake_done, @handshake_timeout
|
|
221
|
+
) do |blk, authenticate, origins, ping_interval, reverify_interval, max_payload_size, db_pool, done, timeout|
|
|
139
222
|
Monk::WebSocket::Server.serve(
|
|
140
223
|
Ractor.receive, blk,
|
|
141
224
|
authenticate: authenticate, allowed_origins: origins, ping_interval: ping_interval,
|
|
142
|
-
reverify_interval: reverify_interval, max_payload_size: max_payload_size, db_pool: db_pool
|
|
225
|
+
reverify_interval: reverify_interval, max_payload_size: max_payload_size, db_pool: db_pool,
|
|
226
|
+
handshake_done: done, handshake_timeout: timeout
|
|
143
227
|
)
|
|
144
228
|
end
|
|
145
229
|
ractor.send(socket, move: true)
|
|
@@ -161,45 +245,63 @@ module Monk
|
|
|
161
245
|
# different failure point in this same shutdown -- a live
|
|
162
246
|
# connection's cleanup racing the registry Ractor's teardown.
|
|
163
247
|
@tcp_server.close
|
|
248
|
+
ensure
|
|
249
|
+
releaser&.kill
|
|
250
|
+
handshake_done&.close
|
|
164
251
|
end
|
|
165
252
|
|
|
166
253
|
# Runs entirely inside the connection's own dedicated Ractor
|
|
167
254
|
# (docs/history/plan-websocket.md Decision 3) -- a class method, not an instance
|
|
168
255
|
# method, since the Server instance itself (holding a live
|
|
169
256
|
# TCPServer) is never Ractor-shareable and can't cross into here.
|
|
257
|
+
#
|
|
258
|
+
# handshake_done: is #run's port for max_handshakes:, told once the
|
|
259
|
+
# handshake is over: the 101 written, or the connection refused or
|
|
260
|
+
# dropped before it.
|
|
170
261
|
def self.serve(
|
|
171
262
|
socket, block, authenticate: false, allowed_origins: nil, ping_interval: nil, reverify_interval: nil,
|
|
172
|
-
max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE, db_pool: nil
|
|
263
|
+
max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE, db_pool: nil, handshake_done: nil,
|
|
264
|
+
handshake_timeout: DEFAULT_HANDSHAKE_TIMEOUT
|
|
173
265
|
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
266
|
+
# A `return` in here (a refused handshake) ends serve, and its
|
|
267
|
+
# ensure frees the slot.
|
|
268
|
+
credential, subject = with_deadline(socket, handshake_timeout) do
|
|
269
|
+
request = read_handshake_request(socket)
|
|
270
|
+
return unless request
|
|
271
|
+
return socket.write("HTTP/1.1 431 Request Header Fields Too Large\r\n\r\n") if request == :too_large
|
|
272
|
+
|
|
273
|
+
headers =
|
|
274
|
+
begin
|
|
275
|
+
Handshake.parse_request(request)
|
|
276
|
+
rescue Monk::WebSocket::HandshakeError
|
|
277
|
+
return socket.write("HTTP/1.1 400 Bad Request\r\n\r\n")
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
subject = nil
|
|
281
|
+
credential = Handshake.credential_from(headers) if authenticate
|
|
282
|
+
case authenticate
|
|
283
|
+
when true
|
|
284
|
+
subject = authenticate!(socket, headers, credential, allowed_origins, db_pool)
|
|
285
|
+
return unless subject
|
|
286
|
+
when :optional
|
|
287
|
+
return unless origin_allowed_for_optional?(socket, headers, credential, allowed_origins)
|
|
288
|
+
|
|
289
|
+
subject = identify(credential, db_pool)
|
|
290
|
+
# Not anonymous: that would quietly sign the user out of their
|
|
291
|
+
# topics until the next reconnect.
|
|
292
|
+
return unavailable!(socket, headers) if subject.equal?(VERIFY_FAILED)
|
|
182
293
|
end
|
|
183
294
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
when true
|
|
187
|
-
subject = authenticate!(socket, headers, allowed_origins, db_pool)
|
|
188
|
-
return unless subject
|
|
189
|
-
when :optional
|
|
190
|
-
return unless origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
191
|
-
|
|
192
|
-
subject = identify(headers, db_pool)
|
|
295
|
+
socket.write(Handshake.response(headers))
|
|
296
|
+
[credential, subject]
|
|
193
297
|
end
|
|
194
|
-
|
|
195
|
-
socket.write(Handshake.response_for(request))
|
|
298
|
+
handshake_done = handshake_over(handshake_done)
|
|
196
299
|
|
|
197
300
|
connection = Connection.new(socket, subject: subject, max_payload_size: max_payload_size)
|
|
198
301
|
connection.start_heartbeat(ping_interval) if ping_interval
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
end
|
|
302
|
+
if reverify_interval && subject
|
|
303
|
+
reverify_thread = start_reverify(connection, credential[:token], reverify_interval, db_pool)
|
|
304
|
+
end
|
|
203
305
|
begin
|
|
204
306
|
block.call(connection)
|
|
205
307
|
rescue StandardError
|
|
@@ -209,11 +311,16 @@ module Monk
|
|
|
209
311
|
# not re-raised -- there's no caller left to hand it to once
|
|
210
312
|
# we're inside this connection's own dedicated Ractor.
|
|
211
313
|
end
|
|
314
|
+
rescue IOError, SystemCallError
|
|
315
|
+
# The client went away, or was shut out by the deadline, before
|
|
316
|
+
# the handshake was over: nothing to answer.
|
|
212
317
|
ensure
|
|
213
318
|
# Runs on every exit path -- normal completion, the close
|
|
214
319
|
# handshake, and a crashed handler alike -- so a subscribed
|
|
215
320
|
# connection never leaks a stale registry entry (step 17), a
|
|
216
321
|
# heartbeat thread, or a reverify thread outliving its socket.
|
|
322
|
+
# A handshake that never reached its 101 still frees its slot.
|
|
323
|
+
handshake_over(handshake_done)
|
|
217
324
|
reverify_thread&.kill
|
|
218
325
|
connection&.unsubscribe!
|
|
219
326
|
connection&.stop_heartbeat!
|
|
@@ -221,6 +328,35 @@ module Monk
|
|
|
221
328
|
disconnect_all
|
|
222
329
|
end
|
|
223
330
|
|
|
331
|
+
# Runs the block with a watchdog that shuts the socket down once
|
|
332
|
+
# `seconds` have passed, which ends a read or write blocked on it (a
|
|
333
|
+
# read returns nil, a write raises). Not IO#timeout=: on Ruby 4.0,
|
|
334
|
+
# once an IO#timeout has fired in one Ractor, reads in Ractors made
|
|
335
|
+
# after it can raise IO::TimeoutError on sockets with no timeout set.
|
|
336
|
+
def self.with_deadline(socket, seconds)
|
|
337
|
+
watchdog = Thread.new do
|
|
338
|
+
sleep seconds
|
|
339
|
+
socket.shutdown(:RDWR)
|
|
340
|
+
rescue IOError, SystemCallError
|
|
341
|
+
# Already closed or gone.
|
|
342
|
+
end
|
|
343
|
+
yield
|
|
344
|
+
ensure
|
|
345
|
+
watchdog&.kill
|
|
346
|
+
end
|
|
347
|
+
private_class_method :with_deadline
|
|
348
|
+
|
|
349
|
+
# Frees this connection's max_handshakes: slot in #run; returns nil,
|
|
350
|
+
# so serve can mark it freed. The port is gone once #run has
|
|
351
|
+
# returned, and then there's no slot to free.
|
|
352
|
+
def self.handshake_over(handshake_done)
|
|
353
|
+
handshake_done&.send(true)
|
|
354
|
+
nil
|
|
355
|
+
rescue Ractor::ClosedError
|
|
356
|
+
nil
|
|
357
|
+
end
|
|
358
|
+
private_class_method :handshake_over
|
|
359
|
+
|
|
224
360
|
# Closes the connections this socket's Ractor opened: its database
|
|
225
361
|
# connection (verifying the session without db_pool:, or a block
|
|
226
362
|
# that queries) and a fan-out's publisher (a block that broadcasts).
|
|
@@ -239,10 +375,9 @@ module Monk
|
|
|
239
375
|
# credential -- a Bearer connection has no Origin header to check
|
|
240
376
|
# by construction (mirrors require_csrf!'s own Bearer exemption in
|
|
241
377
|
# docs/history/plan-auth.md). Returns the verified subject, or nil after writing
|
|
242
|
-
# the appropriate 403/401 response itself
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
378
|
+
# the appropriate 403/401 response itself -- or 503, when verify
|
|
379
|
+
# couldn't answer (docs/plan-ws-connect-ceiling.md, item 3).
|
|
380
|
+
def self.authenticate!(socket, headers, credential, allowed_origins, db_pool)
|
|
246
381
|
if credential&.fetch(:via) == :cookie && allowed_origins
|
|
247
382
|
origin = headers["origin"]
|
|
248
383
|
unless origin && allowed_origins.include?(origin)
|
|
@@ -251,12 +386,26 @@ module Monk
|
|
|
251
386
|
end
|
|
252
387
|
end
|
|
253
388
|
|
|
254
|
-
subject = credential &&
|
|
389
|
+
subject = credential && handshake_verify(credential[:token], db_pool)
|
|
390
|
+
return unavailable!(socket, headers) if subject.equal?(VERIFY_FAILED)
|
|
391
|
+
|
|
255
392
|
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n") unless subject
|
|
256
393
|
subject
|
|
257
394
|
end
|
|
258
395
|
private_class_method :authenticate!
|
|
259
396
|
|
|
397
|
+
# A handshake whose verify couldn't answer: 503, and nil. Browser JS
|
|
398
|
+
# never sees a failed handshake's status or headers (only onclose,
|
|
399
|
+
# 1006), so Retry-After is for the other clients, the ones without
|
|
400
|
+
# an Origin, and jittered, so a wave of them doesn't come back as
|
|
401
|
+
# one.
|
|
402
|
+
def self.unavailable!(socket, headers)
|
|
403
|
+
retry_after = headers["origin"] ? "" : "Retry-After: #{rand(1..10)}\r\n"
|
|
404
|
+
socket.write("HTTP/1.1 503 Service Unavailable\r\n#{retry_after}\r\n")
|
|
405
|
+
nil
|
|
406
|
+
end
|
|
407
|
+
private_class_method :unavailable!
|
|
408
|
+
|
|
260
409
|
# authenticate: :optional's Origin check, before anything is verified.
|
|
261
410
|
# A Bearer connection is exempt, as under true. A cookie needs an
|
|
262
411
|
# allowed Origin, as under true. An anonymous connection that sends an
|
|
@@ -264,10 +413,9 @@ module Monk
|
|
|
264
413
|
# stops another site's page from opening sockets here on its
|
|
265
414
|
# visitors' behalf; one that sends none (not a browser) gets in.
|
|
266
415
|
# Writes the 403 itself and returns false when refused.
|
|
267
|
-
def self.origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
416
|
+
def self.origin_allowed_for_optional?(socket, headers, credential, allowed_origins)
|
|
268
417
|
return true unless allowed_origins
|
|
269
418
|
|
|
270
|
-
credential = Handshake.credential_from(headers)
|
|
271
419
|
return true if credential&.fetch(:via) == :bearer
|
|
272
420
|
|
|
273
421
|
origin = headers["origin"]
|
|
@@ -280,13 +428,26 @@ module Monk
|
|
|
280
428
|
private_class_method :origin_allowed_for_optional?
|
|
281
429
|
|
|
282
430
|
# The subject of a valid session, or nil -- no credential, or one
|
|
283
|
-
# Monk::Auth no longer accepts, both mean anonymous under :optional
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
credential &&
|
|
431
|
+
# Monk::Auth no longer accepts, both mean anonymous under :optional --
|
|
432
|
+
# or VERIFY_FAILED.
|
|
433
|
+
def self.identify(credential, db_pool)
|
|
434
|
+
credential && handshake_verify(credential[:token], db_pool)
|
|
287
435
|
end
|
|
288
436
|
private_class_method :identify
|
|
289
437
|
|
|
438
|
+
# verify, or VERIFY_FAILED with one line on stderr if it raised:
|
|
439
|
+
# under a burst, a backtrace per refused connection buries the log.
|
|
440
|
+
# The message's first line only (a PG error adds the query and a
|
|
441
|
+
# caret), and not Kernel#warn, which -W0 silences.
|
|
442
|
+
def self.handshake_verify(raw_token, db_pool)
|
|
443
|
+
verify(raw_token, db_pool)
|
|
444
|
+
rescue StandardError => e
|
|
445
|
+
reason = e.message.lines.first&.chomp
|
|
446
|
+
$stderr.puts "monk-websocket: handshake refused with 503, verify failed: #{e.class}: #{reason}" # rubocop:disable Style/StderrPuts
|
|
447
|
+
VERIFY_FAILED
|
|
448
|
+
end
|
|
449
|
+
private_class_method :handshake_verify
|
|
450
|
+
|
|
290
451
|
# Sleeps in `interval`-sized steps, re-running Monk::Auth.verify
|
|
291
452
|
# against the same raw credential each time, until it comes back
|
|
292
453
|
# nil (revoked or expired) -- then closes the connection with the
|
|
@@ -319,15 +480,18 @@ module Monk
|
|
|
319
480
|
end
|
|
320
481
|
private_class_method :verify
|
|
321
482
|
|
|
483
|
+
# The request head, nil if the client went away or was too slow, or
|
|
484
|
+
# :too_large past Handshake::MAX_REQUEST_SIZE.
|
|
485
|
+
#
|
|
486
|
+
# One String, read through the socket's own buffer: Connection's
|
|
487
|
+
# reads use that buffer after it, so a frame the client sent right
|
|
488
|
+
# behind its request isn't lost (a readpartial would take it into
|
|
489
|
+
# the request and drop it). A read(1) loop made a String a byte.
|
|
322
490
|
def self.read_handshake_request(socket)
|
|
323
|
-
request =
|
|
324
|
-
|
|
325
|
-
chunk = socket.read(1)
|
|
326
|
-
return nil if chunk.nil?
|
|
491
|
+
request = socket.gets("\r\n\r\n", Handshake::MAX_REQUEST_SIZE)
|
|
492
|
+
return request if request&.end_with?("\r\n\r\n")
|
|
327
493
|
|
|
328
|
-
|
|
329
|
-
end
|
|
330
|
-
request
|
|
494
|
+
:too_large if request && request.bytesize >= Handshake::MAX_REQUEST_SIZE
|
|
331
495
|
end
|
|
332
496
|
private_class_method :read_handshake_request
|
|
333
497
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monkrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.22.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matteo Folin
|
|
@@ -186,6 +186,7 @@ files:
|
|
|
186
186
|
- lib/monk/auth/rate_limiter.rb
|
|
187
187
|
- lib/monk/auth/send_login_link.rb
|
|
188
188
|
- lib/monk/auth/session.rb
|
|
189
|
+
- lib/monk/auth/sweep.rb
|
|
189
190
|
- lib/monk/base.rb
|
|
190
191
|
- lib/monk/bin.rb
|
|
191
192
|
- lib/monk/cli.rb
|
|
@@ -210,9 +211,13 @@ files:
|
|
|
210
211
|
- lib/monk/jobs/adapters/pg.rb
|
|
211
212
|
- lib/monk/jobs/args.rb
|
|
212
213
|
- lib/monk/jobs/claim.rb
|
|
214
|
+
- lib/monk/jobs/cron.rb
|
|
213
215
|
- lib/monk/jobs/errors.rb
|
|
216
|
+
- lib/monk/jobs/firing.rb
|
|
214
217
|
- lib/monk/jobs/job.rb
|
|
218
|
+
- lib/monk/jobs/recurring_task.rb
|
|
215
219
|
- lib/monk/jobs/runtime.rb
|
|
220
|
+
- lib/monk/jobs/scheduler.rb
|
|
216
221
|
- lib/monk/jobs/worker.rb
|
|
217
222
|
- lib/monk/live.rb
|
|
218
223
|
- lib/monk/live/client/idiomorph.LICENSE
|
|
@@ -254,6 +259,8 @@ files:
|
|
|
254
259
|
- lib/monk/templates/auth/config/auth.rb
|
|
255
260
|
- lib/monk/templates/auth/db/migrate/00000000000001_create_auth_tables.down.sql
|
|
256
261
|
- lib/monk/templates/auth/db/migrate/00000000000001_create_auth_tables.up.sql
|
|
262
|
+
- lib/monk/templates/auth/db/migrate/00000000000004_create_login_requests.down.sql
|
|
263
|
+
- lib/monk/templates/auth/db/migrate/00000000000004_create_login_requests.up.sql
|
|
257
264
|
- lib/monk/templates/auth/setup.md
|
|
258
265
|
- lib/monk/templates/auth/test/auth_test.rb
|
|
259
266
|
- lib/monk/templates/base/.dockerignore
|
|
@@ -288,8 +295,11 @@ files:
|
|
|
288
295
|
- lib/monk/templates/jobs/app/routes/jobs.rb
|
|
289
296
|
- lib/monk/templates/jobs/bin/jobs
|
|
290
297
|
- lib/monk/templates/jobs/config/jobs.rb
|
|
298
|
+
- lib/monk/templates/jobs/config/recurring.rb
|
|
291
299
|
- lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.down.sql
|
|
292
300
|
- lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.up.sql
|
|
301
|
+
- lib/monk/templates/jobs/db/migrate/00000000000003_create_recurring_tables.down.sql
|
|
302
|
+
- lib/monk/templates/jobs/db/migrate/00000000000003_create_recurring_tables.up.sql
|
|
293
303
|
- lib/monk/templates/jobs/setup.md
|
|
294
304
|
- lib/monk/templates/jobs/test/jobs_test.rb
|
|
295
305
|
- lib/monk/templates/live/agents.md
|