tina4ruby 3.10.10 → 3.10.12
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/lib/tina4/rack_app.rb +10 -0
- data/lib/tina4/session_handlers/database_handler.rb +6 -0
- data/lib/tina4/session_handlers/file_handler.rb +12 -0
- data/lib/tina4/template.rb +10 -4
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/websocket_backplane.rb +73 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58885d2a44c72f086b6359780fe7f01f88870bf3491973d9f1068934bb8780e1
|
|
4
|
+
data.tar.gz: c567b4d7a4a027d460da4bd528204ffd7ba3ce4aa6e0b3e5d303c748d00f12fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 409514dda53214f3f02d36dacd58b845cd9c282e63e814590be34c13e4cac839e8c8f6c6dc571575c434d0736a055109f12e3b56f76087ceee2d4b046930b1e8
|
|
7
|
+
data.tar.gz: caaa5dc05a96d5061bc3e70af18371d5e3fb7e8c10bd8e03cc84ca47be1863527814d478ec5788654b417d13ee754cc84f575ccb3ec43a5c16826fff798a5a52
|
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -107,6 +107,16 @@ module Tina4
|
|
|
107
107
|
if request_obj&.instance_variable_get(:@session)
|
|
108
108
|
sess = request_obj.session
|
|
109
109
|
sess.save
|
|
110
|
+
|
|
111
|
+
# Probabilistic garbage collection (~1% of requests)
|
|
112
|
+
if rand(1..100) == 1
|
|
113
|
+
begin
|
|
114
|
+
sess.gc
|
|
115
|
+
rescue StandardError
|
|
116
|
+
# GC failure is non-critical — silently ignore
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
110
120
|
sid = sess.id
|
|
111
121
|
cookie_val = (env["HTTP_COOKIE"] || "")[/tina4_session=([^;]+)/, 1]
|
|
112
122
|
if sid && sid != cookie_val
|
|
@@ -56,6 +56,12 @@ module Tina4
|
|
|
56
56
|
@db.execute("DELETE FROM #{TABLE_NAME} WHERE expires_at > 0 AND expires_at < ?", [Time.now.to_f])
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Garbage-collect expired sessions. Matches the Python interface.
|
|
60
|
+
# @param max_age [Integer] maximum session age in seconds (unused — expiry is absolute)
|
|
61
|
+
def gc(max_age)
|
|
62
|
+
@db.execute("DELETE FROM #{TABLE_NAME} WHERE expires_at > 0 AND expires_at < ?", [Time.now.to_f])
|
|
63
|
+
end
|
|
64
|
+
|
|
59
65
|
private
|
|
60
66
|
|
|
61
67
|
def ensure_table
|
|
@@ -44,6 +44,18 @@ module Tina4
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Garbage-collect expired sessions. Matches the Python interface.
|
|
48
|
+
# @param max_age [Integer] maximum session age in seconds
|
|
49
|
+
def gc(max_age)
|
|
50
|
+
return unless Dir.exist?(@dir)
|
|
51
|
+
now = Time.now
|
|
52
|
+
Dir.glob(File.join(@dir, "sess_*")).each do |file|
|
|
53
|
+
File.delete(file) if File.mtime(file) + max_age < now
|
|
54
|
+
rescue StandardError
|
|
55
|
+
# Corrupt or locked file — skip
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
47
59
|
private
|
|
48
60
|
|
|
49
61
|
def session_path(session_id)
|
data/lib/tina4/template.rb
CHANGED
|
@@ -537,13 +537,19 @@ module Tina4
|
|
|
537
537
|
end
|
|
538
538
|
elsif part =~ /\A(\w+)\[(.+?)\]\z/
|
|
539
539
|
base = Regexp.last_match(1)
|
|
540
|
-
index = Regexp.last_match(2)
|
|
540
|
+
index = Regexp.last_match(2).strip
|
|
541
541
|
value = access_value(value, base)
|
|
542
|
-
if index =~ /\A\
|
|
542
|
+
if index =~ /\A["'](.*)["']\z/
|
|
543
|
+
# Quoted string literal — use as-is
|
|
544
|
+
index = Regexp.last_match(1)
|
|
545
|
+
value = access_value(value, index)
|
|
546
|
+
elsif index =~ /\A\d+\z/
|
|
543
547
|
value = value[index.to_i] if value.respond_to?(:[])
|
|
544
548
|
else
|
|
545
|
-
|
|
546
|
-
|
|
549
|
+
# Resolve as a variable from context
|
|
550
|
+
resolved = resolve_variable(index)
|
|
551
|
+
value = access_value(value, resolved.to_s) unless resolved.nil?
|
|
552
|
+
value = nil if resolved.nil?
|
|
547
553
|
end
|
|
548
554
|
else
|
|
549
555
|
value = access_value(value, part)
|
data/lib/tina4/version.rb
CHANGED
|
@@ -56,7 +56,7 @@ module Tina4
|
|
|
56
56
|
when "redis"
|
|
57
57
|
RedisBackplane.new(url: url)
|
|
58
58
|
when "nats"
|
|
59
|
-
|
|
59
|
+
NATSBackplane.new(url: url)
|
|
60
60
|
when ""
|
|
61
61
|
nil
|
|
62
62
|
else
|
|
@@ -115,4 +115,76 @@ module Tina4
|
|
|
115
115
|
@redis.close
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
|
+
|
|
119
|
+
# NATS pub/sub backplane.
|
|
120
|
+
#
|
|
121
|
+
# Requires the +nats-pure+ gem (+gem install nats-pure+). The require is
|
|
122
|
+
# deferred so the rest of Tina4 works fine without it installed — an error
|
|
123
|
+
# is raised only when this class is actually instantiated.
|
|
124
|
+
#
|
|
125
|
+
# NATS is async-native, so we run a background thread with an event
|
|
126
|
+
# machine for the subscription listener.
|
|
127
|
+
class NATSBackplane < WebSocketBackplane
|
|
128
|
+
def initialize(url: nil)
|
|
129
|
+
begin
|
|
130
|
+
require "nats/client"
|
|
131
|
+
rescue LoadError
|
|
132
|
+
raise LoadError,
|
|
133
|
+
"The 'nats-pure' gem is required for NATSBackplane. " \
|
|
134
|
+
"Install it with: gem install nats-pure"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@url = url || ENV.fetch("TINA4_WS_BACKPLANE_URL", "nats://localhost:4222")
|
|
138
|
+
@subs = {}
|
|
139
|
+
@threads = {}
|
|
140
|
+
@running = true
|
|
141
|
+
@mutex = Mutex.new
|
|
142
|
+
|
|
143
|
+
# Connect to NATS in a background thread with its own event loop
|
|
144
|
+
@nats = NATS::IO::Client.new
|
|
145
|
+
@nats.connect(@url)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def publish(channel, message)
|
|
149
|
+
@nats.publish(channel, message)
|
|
150
|
+
@nats.flush
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def subscribe(channel, &block)
|
|
154
|
+
@mutex.synchronize do
|
|
155
|
+
sid = @nats.subscribe(channel) do |msg|
|
|
156
|
+
block.call(msg.data) if @running
|
|
157
|
+
end
|
|
158
|
+
@subs[channel] = sid
|
|
159
|
+
|
|
160
|
+
# Run NATS event processing in a background thread
|
|
161
|
+
@threads[channel] ||= Thread.new do
|
|
162
|
+
loop do
|
|
163
|
+
break unless @running
|
|
164
|
+
sleep 0.01
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def unsubscribe(channel)
|
|
171
|
+
@mutex.synchronize do
|
|
172
|
+
sid = @subs.delete(channel)
|
|
173
|
+
@nats.unsubscribe(sid) if sid
|
|
174
|
+
thread = @threads.delete(channel)
|
|
175
|
+
thread&.kill
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def close
|
|
180
|
+
@running = false
|
|
181
|
+
@mutex.synchronize do
|
|
182
|
+
@subs.each_value { |sid| @nats.unsubscribe(sid) rescue nil }
|
|
183
|
+
@subs.clear
|
|
184
|
+
@threads.each_value { |t| t.kill }
|
|
185
|
+
@threads.clear
|
|
186
|
+
end
|
|
187
|
+
@nats.close
|
|
188
|
+
end
|
|
189
|
+
end
|
|
118
190
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.10.
|
|
4
|
+
version: 3.10.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rack
|
|
@@ -400,7 +399,6 @@ licenses:
|
|
|
400
399
|
- MIT
|
|
401
400
|
metadata:
|
|
402
401
|
homepage_uri: https://tina4.com
|
|
403
|
-
post_install_message:
|
|
404
402
|
rdoc_options: []
|
|
405
403
|
require_paths:
|
|
406
404
|
- lib
|
|
@@ -415,8 +413,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
415
413
|
- !ruby/object:Gem::Version
|
|
416
414
|
version: '0'
|
|
417
415
|
requirements: []
|
|
418
|
-
rubygems_version:
|
|
419
|
-
signing_key:
|
|
416
|
+
rubygems_version: 4.0.3
|
|
420
417
|
specification_version: 4
|
|
421
418
|
summary: Simple. Fast. Human. This is not a framework.
|
|
422
419
|
test_files: []
|