Class: Tina4::SessionHandlers::RespClient

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/session_handlers/resp_client.rb

Overview

Zero-dependency synchronous RESP client over a TCP socket. Used by the Redis and Valkey session handlers as the fallback when the optional ‘redis` gem is not installed, so a Tina4 app talks to Redis/Valkey for sessions with NO third-party gem. Parity with the Python (redis-py + raw RESP) and Node (redis npm + raw respClient) session handlers, and with the framework’s own cache backends, which already speak raw RESP this way.

One short-lived connection per command (open -> AUTH? -> SELECT? -> command -> close), matching the cache backends. Ruby’s blocking sockets make the reply reader straightforward: read the header line up to CRLF, then read the bulk body by its exact byte count (so a large session value spanning several TCP segments is read in full, not truncated to one recv()).

Instance Method Summary collapse

Constructor Details

#initialize(host:, port:, password: nil, db: 0, timeout: 5) ⇒ RespClient

Returns a new instance of RespClient.



24
25
26
27
28
29
30
# File 'lib/tina4/session_handlers/resp_client.rb', line 24

def initialize(host:, port:, password: nil, db: 0, timeout: 5)
  @host = host
  @port = port
  @password = password
  @db = (db || 0).to_i
  @timeout = timeout
end

Instance Method Details

#command(*args) ⇒ Object

Send one RESP command and return its reply: a String for a simple/bulk string or integer, an Array for a multi-bulk reply, or nil for a nil bulk ($-1) / miss. A transport failure (server unreachable, rejected AUTH or SELECT, timeout, connection closed mid-reply) RAISES so the Session boundary can log-loud + degrade (a real op never silently no-ops).



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/tina4/session_handlers/resp_client.rb', line 53

def command(*args)
  sock = Socket.tcp(@host, @port, connect_timeout: @timeout)
  sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [@timeout, 0].pack("l_2"))
  begin
    if @password && !@password.empty?
      write_command(sock, "AUTH", @password)
      reply = read_reply(sock)
      raise RespError, "AUTH rejected: #{reply.message}" if reply.is_a?(RespError)
    end
    if @db != 0
      write_command(sock, "SELECT", @db.to_s)
      reply = read_reply(sock)
      raise RespError, "SELECT rejected: #{reply.message}" if reply.is_a?(RespError)
    end
    write_command(sock, *args)
    reply = read_reply(sock)
    raise reply if reply.is_a?(RespError)
    reply
  ensure
    begin
      sock.close
    rescue StandardError
      # already closed / never opened — nothing to do
    end
  end
end

#del(key) ⇒ Object



44
45
46
# File 'lib/tina4/session_handlers/resp_client.rb', line 44

def del(key)
  command("DEL", key)
end

#get(key) ⇒ Object



32
33
34
# File 'lib/tina4/session_handlers/resp_client.rb', line 32

def get(key)
  command("GET", key)
end

#set(key, value) ⇒ Object



36
37
38
# File 'lib/tina4/session_handlers/resp_client.rb', line 36

def set(key, value)
  command("SET", key, value)
end

#setex(key, ttl, value) ⇒ Object



40
41
42
# File 'lib/tina4/session_handlers/resp_client.rb', line 40

def setex(key, ttl, value)
  command("SETEX", key, ttl.to_s, value)
end