Skip to content

Commit

Permalink
Merge pull request #88 from rage-rb/websockets
Browse files Browse the repository at this point in the history
Web... wait for it... sockets!
  • Loading branch information
rsamoilov authored Aug 5, 2024
2 parents 005c9fe + c8707f5 commit 05e783f
Show file tree
Hide file tree
Showing 31 changed files with 2,500 additions and 22 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ group :test do
gem "connection_pool", "~> 2.0"
gem "rbnacl"
gem "domain_name"
gem "websocket-client-simple"
end
38 changes: 23 additions & 15 deletions lib/rage-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,17 @@

module Rage
def self.application
app = Application.new(__router)

config.middleware.middlewares.reverse.inject(app) do |next_in_chain, (middleware, args, block)|
# in Rails compatibility mode we first check if the middleware is a part of the Rails middleware stack;
# if it is - it is expected to be built using `ActionDispatch::MiddlewareStack::Middleware#build`
if Rage.config.internal.rails_mode
rails_middleware = Rails.application.config.middleware.middlewares.find { |m| m.name == middleware.name }
end

if rails_middleware
rails_middleware.build(next_in_chain)
else
middleware.new(next_in_chain, *args, &block)
end
end
with_middlewares(Application.new(__router), config.middleware.middlewares)
end

def self.multi_application
Rage::Router::Util::Cascade.new(application, Rails.application)
end

def self.cable
Rage::Cable
end

def self.routes
Rage::Router::DSL.new(__router)
end
Expand Down Expand Up @@ -90,6 +80,23 @@ def self.patch_active_record_connection_pool
end
end

# @private
def self.with_middlewares(app, middlewares)
middlewares.reverse.inject(app) do |next_in_chain, (middleware, args, block)|
# in Rails compatibility mode we first check if the middleware is a part of the Rails middleware stack;
# if it is - it is expected to be built using `ActionDispatch::MiddlewareStack::Middleware#build`
if Rage.config.internal.rails_mode
rails_middleware = Rails.application.config.middleware.middlewares.find { |m| m.name == middleware.name }
end

if rails_middleware
rails_middleware.build(next_in_chain)
else
middleware.new(next_in_chain, *args, &block)
end
end
end

module Router
module Strategies
end
Expand All @@ -106,6 +113,7 @@ module ActiveRecord

autoload :Cookies, "rage/cookies"
autoload :Session, "rage/session"
autoload :Cable, "rage/cable/cable"
end

module RageController
Expand Down
1 change: 1 addition & 0 deletions lib/rage/all.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
require_relative "logger/json_formatter"
require_relative "logger/logger"

require_relative "middleware/origin_validator"
require_relative "middleware/fiber_wrapper"
require_relative "middleware/cors"
require_relative "middleware/reloader"
Expand Down
130 changes: 130 additions & 0 deletions lib/rage/cable/cable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# frozen_string_literal: true

module Rage::Cable
# Create a new Cable application.
#
# @example
# map "/cable" do
# run Rage.cable.application
# end
def self.application
protocol = Rage.config.cable.protocol
protocol.init(__router)

handler = __build_handler(protocol)
accept_response = [0, protocol.protocol_definition, []]

application = ->(env) do
if env["rack.upgrade?"] == :websocket
env["rack.upgrade"] = handler
accept_response
else
[426, { "Connection" => "Upgrade", "Upgrade" => "websocket" }, []]
end
end

Rage.with_middlewares(application, Rage.config.cable.middlewares)
end

# @private
def self.__router
@__router ||= Router.new
end

# @private
def self.__build_handler(protocol)
klass = Class.new do
def initialize(protocol)
Iodine.on_state(:on_start) do
unless Fiber.scheduler
Fiber.set_scheduler(Rage::FiberScheduler.new)
end
end

@protocol = protocol
end

def on_open(connection)
Fiber.schedule do
@protocol.on_open(connection)
rescue => e
log_error(e)
end
end

def on_message(connection, data)
Fiber.schedule do
@protocol.on_message(connection, data)
rescue => e
log_error(e)
end
end

if protocol.respond_to?(:on_close)
def on_close(connection)
return unless ::Iodine.running?

Fiber.schedule do
@protocol.on_close(connection)
rescue => e
log_error(e)
end
end
end

if protocol.respond_to?(:on_shutdown)
def on_shutdown(connection)
@protocol.on_shutdown(connection)
rescue => e
log_error(e)
end
end

private

def log_error(e)
Rage.logger.error("Unhandled exception has occured - #{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
end
end

klass.new(protocol)
end

# Broadcast data directly to a named stream.
#
# @param stream [String] the name of the stream
# @param data [Object] the object to send to the clients. This will later be encoded according to the protocol used.
# @example
# Rage.cable.broadcast("chat", { message: "A new member has joined!" })
def self.broadcast(stream, data)
Rage.config.cable.protocol.broadcast(stream, data)
end

# @!parse [ruby]
# # @abstract
# class WebSocketConnection
# # Write data to the connection.
# #
# # @param data [String] the data to write
# def write(data)
# end
#
# # Subscribe to a channel.
# #
# # @param name [String] the channel name
# def subscribe(name)
# end
#
# # Close the connection.
# def close
# end
# end

module Protocol
end
end

require_relative "protocol/actioncable_v1_json"
require_relative "channel"
require_relative "connection"
require_relative "router"
Loading

0 comments on commit 05e783f

Please sign in to comment.