Class: DuoRuby::Server
- Defined in:
- lib/duoruby/server.rb,
lib/duoruby/server/frontend_compiler.rb
Overview
Application server built on Falcon and Async.
Server handles three HTTP routes:
-
GET / — serves an HTML shell page that loads the frontend script
-
GET /duoruby/app.js — compiles and serves the Opal frontend on demand
-
GET /duoruby/socket — upgrades to a WebSocket and drives message handlers
Subclasses can declare message handlers with on and can override #call for custom HTTP routes before delegating to super.
Defined Under Namespace
Classes: FrontendCompiler
Constant Summary collapse
- SOCKET_PATH =
Path that the browser WebSocket connects to.
"/duoruby/socket"- SCRIPT_PATH =
Path from which the compiled frontend JavaScript is served.
"/duoruby/app.js"
Instance Attribute Summary collapse
-
#groups ⇒ Hash{Symbol => Group}
readonly
All groups that have been accessed on this server.
-
#host ⇒ String
readonly
The bind host.
-
#port ⇒ Integer
readonly
The bind port.
-
#root ⇒ String
readonly
The expanded application root directory.
Attributes inherited from Channel
Class Method Summary collapse
Instance Method Summary collapse
- #authenticate(_client) ⇒ Object
- #broadcast(group_name, event, **params) ⇒ Object
-
#call(request) ⇒ Protocol::HTTP::Response
Rack-compatible request handler.
- #configure(root:, host:, port:) ⇒ Object
- #connect(id:, writer: nil, metadata: {}, &writer_block) ⇒ Object
- #disconnect(client) ⇒ Object
-
#frontend_javascript ⇒ String
Compiles the Opal frontend to a JavaScript string.
- #group(name) ⇒ Object
-
#initialize(root: Dir.pwd, host: "127.0.0.1", port: 9292) ⇒ Server
constructor
A new instance of Server.
-
#launch(output: $stdout, title: nil, width: 1280, height: 800) ⇒ Object
Forks a native browser window into a child process, then runs this server in the main process.
- #receive(client, message) ⇒ Object
-
#run(output: $stdout) ⇒ Object
Starts the Falcon server and blocks until it exits.
Methods inherited from Channel
#channel, #dispatch, #handler_for, inherited, #off, #on, #one
Methods included from Channel::HandlerMethods
#channel, #handlers, included, #included, #off, #on, #one
Constructor Details
#initialize(root: Dir.pwd, host: "127.0.0.1", port: 9292) ⇒ Server
Returns a new instance of Server.
57 58 59 60 61 62 |
# File 'lib/duoruby/server.rb', line 57 def initialize(root: Dir.pwd, host: "127.0.0.1", port: 9292) super() configure(root: root, host: host, port: port) @groups = {} @next_client_id = 0 end |
Instance Attribute Details
#groups ⇒ Hash{Symbol => Group} (readonly)
Returns all groups that have been accessed on this server.
52 53 54 |
# File 'lib/duoruby/server.rb', line 52 def groups @groups end |
#host ⇒ String (readonly)
Returns the bind host.
46 47 48 |
# File 'lib/duoruby/server.rb', line 46 def host @host end |
#port ⇒ Integer (readonly)
Returns the bind port.
49 50 51 |
# File 'lib/duoruby/server.rb', line 49 def port @port end |
#root ⇒ String (readonly)
Returns the expanded application root directory.
43 44 45 |
# File 'lib/duoruby/server.rb', line 43 def root @root end |
Class Method Details
.build(root: Dir.pwd, host: "127.0.0.1", port: 9292) ⇒ Object
64 65 66 67 68 69 |
# File 'lib/duoruby/server.rb', line 64 def self.build(root: Dir.pwd, host: "127.0.0.1", port: 9292) root = File.(root) server = DuoRuby.load_app(:backend, root: root) || new(root: root, host: host, port: port) server.configure(root: root, host: host, port: port) server end |
Instance Method Details
#authenticate(_client) ⇒ Object
165 166 167 |
# File 'lib/duoruby/server.rb', line 165 def authenticate(_client) true end |
#broadcast(group_name, event, **params) ⇒ Object
180 181 182 |
# File 'lib/duoruby/server.rb', line 180 def broadcast(group_name, event, **params) group(group_name).send(event, **params) end |
#call(request) ⇒ Protocol::HTTP::Response
Rack-compatible request handler. Routes to the appropriate private handler or returns a 404. Catches StandardError and responds with a 500.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/duoruby/server.rb', line 87 def call(request) path = request.path.to_s.split("?", 2).first case path when SOCKET_PATH websocket(request) || not_found("websocket endpoint") when SCRIPT_PATH javascript when "/", "" html else not_found(path) end rescue StandardError => error text(500, "#{error.class}: #{error.}\n") end |
#configure(root:, host:, port:) ⇒ Object
71 72 73 74 75 76 77 78 79 80 |
# File 'lib/duoruby/server.rb', line 71 def configure(root:, host:, port:) @root = File.(root) config_path = File.join(@root, "duoruby.rb") load config_path if File.file?(config_path) @host = host @port = Integer(port) DuoRuby.config.host = @host DuoRuby.config.port = @port self end |
#connect(id:, writer: nil, metadata: {}, &writer_block) ⇒ Object
157 158 159 160 161 162 163 |
# File 'lib/duoruby/server.rb', line 157 def connect(id:, writer: nil, metadata: {}, &writer_block) client = Client.new(id: id, writer: writer, metadata: , &writer_block) return client.reject unless authenticate(client) dispatch(:$connect, client) client end |
#disconnect(client) ⇒ Object
169 170 171 172 173 174 |
# File 'lib/duoruby/server.rb', line 169 def disconnect(client) dispatch(:$disconnect, client) client.cancel_pending_calls client.groups.values.each { |group| group.remove(client) } client end |
#frontend_javascript ⇒ String
Compiles the Opal frontend to a JavaScript string.
Resets Opal’s global path state, adds configured frontend gems, then builds the opal runtime followed by setup/frontend.
Note: this method mutates global Opal state (Opal.reset_paths!) and is not safe to call concurrently.
153 154 155 |
# File 'lib/duoruby/server.rb', line 153 def frontend_javascript FrontendCompiler.new(root).call end |
#group(name) ⇒ Object
176 177 178 |
# File 'lib/duoruby/server.rb', line 176 def group(name) groups[name.to_sym] ||= Group.new(name) end |
#launch(output: $stdout, title: nil, width: 1280, height: 800) ⇒ Object
Forks a native browser window into a child process, then runs this server in the main process. Blocks until the server exits or the window closes.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/duoruby/server.rb', line 126 def launch(output: $stdout, title: nil, width: 1280, height: 800) output.puts "launching http://#{host}:#{port}" browser_pid = fork_process { run_browser(title: title, width: width, height: height) } browser_watchdog = start_browser_watchdog(browser_pid) null_output = File.open(File::NULL, "w") run(output: null_output) rescue Interrupt nil ensure null_output&.close if browser_pid browser_watchdog&.kill terminate_process(browser_pid) end end |
#receive(client, message) ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/duoruby/server.rb', line 184 def receive(client, ) = Message.coerce() return client.resolve_call() if .event == Message::REPLY_EVENT return client.reject_call() if .event == Message::ERROR_EVENT && .reply_to results = dispatch(.event, client, **.params) client.deliver(Message.reply(.id, results.last)) if .id results rescue StandardError => error raise unless &.id client.deliver(Message.error(code: error.class.name, message: error., reply_to: .id)) end |
#run(output: $stdout) ⇒ Object
Starts the Falcon server and blocks until it exits.
107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/duoruby/server.rb', line 107 def run(output: $stdout) endpoint = Async::HTTP::Endpoint.parse("http://#{host}:#{port}") Sync do task = Falcon::Server.new(self, endpoint).run output.puts "serving http://#{host}:#{port}" task.wait ensure task&.stop end end |