Skip to content

Commit 4c57275

Browse files
authored
Merge pull request #89 from /issues/88
Big refactor to make the backend registerable and swappable
2 parents f68b822 + 2f1360c commit 4c57275

10 files changed

Lines changed: 64 additions & 29 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
jobs:
1010
specs:
1111
env:
12-
REDIS_URL: redis://redis:6379
12+
CABLE_BACKEND_URL: redis://redis:6379
1313
strategy:
1414
fail-fast: false
1515
matrix:
@@ -40,9 +40,9 @@ jobs:
4040
--health-timeout 5s
4141
--health-retries 5
4242
steps:
43-
- uses: actions/checkout@v3
43+
- uses: actions/checkout@v4
4444
- name: Cache Crystal
45-
uses: actions/cache@v3
45+
uses: actions/cache@v4
4646
with:
4747
path: ~/.cache/crystal
4848
key: ${{ runner.os }}-crystal

spec/spec_helper.cr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ require "./support/channels/*"
1212
Cable.configure do |settings|
1313
settings.route = "/updates"
1414
settings.token = "test_token"
15-
settings.redis_ping_interval = 2.seconds
15+
settings.url = ENV.fetch("CABLE_BACKEND_URL", "redis://localhost:6379")
16+
settings.backend_class = Cable::RedisBackend
17+
settings.backend_ping_interval = 2.seconds
1618
settings.restart_error_allowance = 2
1719
settings.on_error = ->(exception : Exception, message : String) do
1820
FakeExceptionService.notify(exception, message: message)

src/backend/dev/backend.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ module Cable
3838
@@subscriptions.delete(stream_identifier)
3939
end
4040

41-
def ping_redis_subscribe
41+
def ping_subscribe_connection
4242
end
4343

44-
def ping_redis_publish
44+
def ping_publish_connection
4545
end
4646
end
4747
end

src/backend/redis/backend.cr

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
require "redis"
2+
13
module Cable
24
class RedisBackend < Cable::BackendCore
5+
register "redis" # redis://
6+
register "rediss" # rediss://
7+
38
# connection management
49
getter redis_subscribe : Redis::Connection = Redis::Connection.new(URI.parse(Cable.settings.url))
510
getter redis_publish : Redis::Client = Redis::Client.new(URI.parse(Cable.settings.url))
@@ -72,13 +77,13 @@ module Cable
7277
# then publish a special channel/message broadcast
7378
# the @server.redis_subscribe picks up this special combination
7479
# and calls ping on the block loop for us
75-
def ping_redis_subscribe
80+
def ping_subscribe_connection
7681
Cable.server.publish(Cable::INTERNAL[:channel], "ping")
7782
end
7883

79-
def ping_redis_publish
84+
def ping_publish_connection
8085
result = redis_publish.run({"ping"})
81-
Cable::Logger.debug { "Cable::RedisPinger.ping_redis_publish -> #{result}" }
86+
Cable::Logger.debug { "Cable::BackendPinger.ping_publish_connection -> #{result}" }
8287
end
8388
end
8489
end

src/backend/redis/legacy/backend.cr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@
106106

107107
# ping/pong
108108

109-
def ping_redis_subscribe
109+
def ping_subscribe_connection
110110
Cable.server.publish("_internal", "ping")
111111
end
112112

113-
def ping_redis_publish
113+
def ping_publish_connection
114114
request = Redis::Request.new
115115
request << "ping"
116116
result = redis_subscribe._connection.send(request)
117-
Cable::Logger.debug { "Cable::RedisPinger.ping_redis_publish -> #{result}" }
117+
Cable::Logger.debug { "Cable::BackendPinger.ping_publish_connection -> #{result}" }
118118
end
119119
end
120120
end

src/cable.cr

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
require "habitat"
22
require "json"
3-
require "redis"
43
require "./cable/**"
54

65
# TODO: Write documentation for `Cable`
@@ -30,10 +29,10 @@ module Cable
3029
Habitat.create do
3130
setting route : String = Cable.message(:default_mount_path), example: "/cable"
3231
setting token : String = "token", example: "token"
33-
setting url : String = ENV.fetch("REDIS_URL", "redis://localhost:6379"), example: "redis://localhost:6379"
32+
setting url : String = ENV["CABLE_BACKEND_URL"], example: "redis://localhost:6379"
3433
setting disable_sec_websocket_protocol_header : Bool = false
35-
setting backend_class : Cable::BackendCore.class = Cable::RedisBackend, example: "Cable::RedisBackend"
36-
setting redis_ping_interval : Time::Span = 15.seconds
34+
setting backend_class : Cable::BackendCore.class = Cable::BackendRegistry, example: "Cable::RedisBackend"
35+
setting backend_ping_interval : Time::Span = 15.seconds
3736
setting restart_error_allowance : Int32 = 20
3837
setting on_error : Proc(Exception, String, Nil) = ->(exception : Exception, message : String) do
3938
Cable::Logger.error(exception: exception) { message }

src/cable/backend_core.cr

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
module Cable
22
abstract class BackendCore
3+
def self.register(uri_scheme : String, backend : BackendCore.class = self)
4+
::Cable::BackendRegistry.register uri_scheme, backend
5+
end
6+
37
# connection management
48
abstract def subscribe_connection
59
abstract def publish_connection
@@ -17,8 +21,35 @@ module Cable
1721
abstract def unsubscribe(stream_identifier : String)
1822

1923
# ping/pong
24+
abstract def ping_subscribe_connection
25+
abstract def ping_publish_connection
26+
end
27+
28+
class BackendRegistry < BackendCore
29+
REGISTERED_BACKENDS = {} of String => BackendCore.class
30+
31+
def self.register(uri_scheme : String, backend : BackendCore.class = self)
32+
REGISTERED_BACKENDS[uri_scheme] = backend
33+
end
34+
35+
@backend : BackendCore
36+
37+
def initialize
38+
@backend = REGISTERED_BACKENDS[URI.parse(::Cable.settings.url).scheme].new
39+
end
2040

21-
abstract def ping_redis_subscribe
22-
abstract def ping_redis_publish
41+
delegate(
42+
subscribe_connection,
43+
publish_connection,
44+
close_subscribe_connection,
45+
close_publish_connection,
46+
open_subscribe_connection,
47+
publish_message,
48+
subscribe,
49+
unsubscribe,
50+
ping_subscribe_connection,
51+
ping_publish_connection,
52+
to: @backend
53+
)
2354
end
2455
end
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
require "tasker"
22

33
module Cable
4-
class RedisPinger
4+
class BackendPinger
55
private getter task : Tasker::Task
66

77
def initialize(@server : Cable::Server)
8-
@task = Tasker.every(Cable.settings.redis_ping_interval) do
9-
@server.backend.ping_redis_subscribe
10-
@server.backend.ping_redis_publish
8+
@task = Tasker.every(Cable.settings.backend_ping_interval) do
9+
@server.backend.ping_subscribe_connection
10+
@server.backend.ping_publish_connection
1111
rescue e
1212
stop
13-
Cable::Logger.error { "Cable::RedisPinger Exception: #{e.class.name} -> #{e.message}" }
13+
Cable::Logger.error { "Cable::BackendPinger Exception: #{e.class.name} -> #{e.message}" }
1414
# Restart cable if something happened
1515
Cable.server.count_error!
1616
Cable.restart if Cable.server.restart?

src/cable/channel.cr

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module Cable
22
class Channel
3-
class CloseRedisFiber < Exception; end
4-
53
CHANNELS = {} of String => Cable::Channel.class
64

75
macro inherited

src/cable/server.cr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ module Cable
2727
getter connections = {} of String => Cable::Connection
2828
getter errors = 0
2929
getter fiber_channel = ::Channel({String, String}).new
30-
getter pinger : Cable::RedisPinger do
31-
Cable::RedisPinger.new(self)
30+
getter pinger : Cable::BackendPinger do
31+
Cable::BackendPinger.new(self)
3232
end
3333
getter backend : Cable::BackendCore do
3434
Cable.settings.backend_class.new
@@ -112,7 +112,7 @@ module Cable
112112
end
113113
end
114114

115-
# redis only accepts strings, so we should be strict here
115+
# Some backends only accept strings, so we should be strict here
116116
def publish(channel : String, message : String)
117117
backend.publish_message(channel, message)
118118
end
@@ -169,7 +169,7 @@ module Cable
169169
backend.close_publish_connection
170170
rescue e : IO::Error
171171
# the @writer IO is closed already
172-
Cable::Logger.debug { "Cable::Server#shutdown Connection to redis was severed: #{e.message}" }
172+
Cable::Logger.debug { "Cable::Server#shutdown Connection to backend was severed: #{e.message}" }
173173
end
174174
pinger.stop
175175
connections.each do |_k, v|

0 commit comments

Comments
 (0)