Class: OsCtld::Generic::ClientHandler
- Inherits:
-
Object
- Object
- OsCtld::Generic::ClientHandler
- Includes:
- OsCtl::Lib::Utils::Exception, OsCtl::Lib::Utils::Log
- Defined in:
- lib/osctld/generic/client_handler.rb
Overview
Generic client handler for Server
This class cannot be used directly, you need to subclass it and implement template methods that will provide your logic. This class implements only the generic communication protocol.
Protocol
The protocol is line-based, each line containing a JSON formatted message. Client with the server can negoatiate a different protocol and hijack the socket, e.g. this is used when the client is attaching a console or executing command within a container.
Upon connection, the server sends the client its version, if the server implementation provides it:
{version: "version"}
The client may decide to close the connection when an unsupported version is detected.
When the version is accepted, the client sends a command:
{cmd: <command>, opts: <command parameters>}
The client waits for the server to reply. While waiting for the final response, the client can receive a progress update:
{status: true, progress: "message"}
There can be zero or multiple progress updates, followed by a final response:
{status: true, response: <command response>}
{status: false, message: "error message"}
Based on the response, the client either exits, sends another command, or the connection can be hijacked and another communication protocol may be used.
Direct Known Subclasses
Daemon::ClientHandler, SendReceive::Server::ClientHandler, UserControl::Supervisor::ClientHandler, UserControl::Supervisor::NamespacedClientHandler
Instance Attribute Summary collapse
-
#opts ⇒ Object
readonly
Returns the value of attribute opts.
Instance Method Summary collapse
- #communicate ⇒ Object
-
#error(msg) ⇒ Object
Signal error ‘msg`.
-
#error!(msg) ⇒ Object
Signal error ‘msg`, raises an exception.
-
#handle_cmd(req) ⇒ {status: true, output: any}, ...
Handle client command and return a response or an error.
-
#initialize(socket, opts) ⇒ ClientHandler
constructor
A new instance of ClientHandler.
-
#ok(output = nil) ⇒ Object
Signal command success, send ‘output` to the client.
- #parse(data) ⇒ Object protected
- #reply_error(err) ⇒ Object
- #reply_ok(res) ⇒ Object
-
#request_stop ⇒ Object
Stop the client thread if possible.
- #send_data(data) ⇒ Object protected
- #send_update(msg) ⇒ Object
-
#server_version ⇒ String
Return the server version that is sent to the client in the first message.
- #socket ⇒ Object
Constructor Details
#initialize(socket, opts) ⇒ ClientHandler
Returns a new instance of ClientHandler.
48 49 50 51 |
# File 'lib/osctld/generic/client_handler.rb', line 48 def initialize(socket, opts) @sock = socket @opts = opts end |
Instance Attribute Details
#opts ⇒ Object (readonly)
Returns the value of attribute opts.
46 47 48 |
# File 'lib/osctld/generic/client_handler.rb', line 46 def opts @opts end |
Instance Method Details
#communicate ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/osctld/generic/client_handler.rb', line 53 def communicate v = server_version send_data({ version: v }) if v loop do buf = '' while (m = @sock.recv(1024)) buf += m break if m.empty? || m.end_with?("\n") end break if buf.empty? break if parse(buf) == :handled end rescue Errno::ECONNRESET # pass end |
#error(msg) ⇒ Object
Signal error ‘msg`
104 105 106 |
# File 'lib/osctld/generic/client_handler.rb', line 104 def error(msg) { status: false, message: msg } end |
#error!(msg) ⇒ Object
Signal error ‘msg`, raises an exception
109 110 111 |
# File 'lib/osctld/generic/client_handler.rb', line 109 def error!(msg) raise CommandFailed, msg end |
#handle_cmd(req) ⇒ {status: true, output: any}, ...
88 89 90 |
# File 'lib/osctld/generic/client_handler.rb', line 88 def handle_cmd(req) raise NotImplementedError end |
#ok(output = nil) ⇒ Object
Signal command success, send ‘output` to the client
99 100 101 |
# File 'lib/osctld/generic/client_handler.rb', line 99 def ok(output = nil) { status: true, output: } end |
#parse(data) ⇒ Object (protected)
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/osctld/generic/client_handler.rb', line 131 def parse(data) begin req = JSON.parse(data, symbolize_names: true) rescue TypeError, JSON::ParserError return error('syntax error, expected a valid JSON') end log(:debug, self, "Received command '#{req[:cmd]}'") begin ret = handle_cmd(req) unless ret.is_a?(Hash) log(:fatal, self, "Unrecognized return value #{ret.class}, expected Hash") reply_error('internal error') return end if ret[:status] === true reply_ok(ret[:output]) elsif ret[:status] === :handled log(:debug, self, 'Connection hijacked') return :handled elsif !ret[:message] log(:fatal, self, 'Command failed, but no error message provided') reply_error('internal error') else reply_error(ret[:message]) end rescue CommandFailed, ResourceLocked => e reply_error(e.) rescue DeadlockDetected => e log(:fatal, self, 'Possible deadlock detected') log(:fatal, self, denixstorify(e.backtrace).join("\n")) LockRegistry.dump reply_error('internal error') rescue StandardError => e log(:fatal, self, "Error during command execution: #{e.}") log(:fatal, self, denixstorify(e.backtrace).join("\n")) reply_error('internal error') end true end |
#reply_error(err) ⇒ Object
117 118 119 |
# File 'lib/osctld/generic/client_handler.rb', line 117 def reply_error(err) send_data({ status: false, message: err }) end |
#reply_ok(res) ⇒ Object
121 122 123 |
# File 'lib/osctld/generic/client_handler.rb', line 121 def reply_ok(res) send_data({ status: true, response: res }) end |
#request_stop ⇒ Object
Stop the client thread if possible
This method is not called from the client thread, so the implementation has to communicate with the thread and tell it to quit.
96 |
# File 'lib/osctld/generic/client_handler.rb', line 96 def request_stop; end |
#send_data(data) ⇒ Object (protected)
179 180 181 182 183 184 |
# File 'lib/osctld/generic/client_handler.rb', line 179 def send_data(data) @sock.puts(data.to_json) true rescue Errno::EPIPE false end |
#send_update(msg) ⇒ Object
113 114 115 |
# File 'lib/osctld/generic/client_handler.rb', line 113 def send_update(msg) send_data({ status: true, progress: msg }) end |
#server_version ⇒ String
Return the server version that is sent to the client in the first message. By default, no version is sent to the client.
75 76 77 |
# File 'lib/osctld/generic/client_handler.rb', line 75 def server_version nil end |
#socket ⇒ Object
125 126 127 |
# File 'lib/osctld/generic/client_handler.rb', line 125 def socket @sock end |