Skip to content

Commit

Permalink
Add websockets
Browse files Browse the repository at this point in the history
  • Loading branch information
rsamoilov committed Jun 12, 2024
1 parent 977fe3c commit 484e7d2
Show file tree
Hide file tree
Showing 11 changed files with 945 additions and 21 deletions.
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 @@ -103,6 +110,7 @@ module ActiveRecord

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

module RageController
Expand Down
111 changes: 111 additions & 0 deletions lib/rage/cable/cable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# 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)
@protocol = protocol
end

def on_open(connection)
Fiber.schedule { @protocol.on_open(connection) }
end

def on_message(connection, data)
Fiber.schedule { @protocol.on_message(connection, data) }
end

if protocol.respond_to?(:on_close)
def on_close(connection)
if ::Iodine.running?
Fiber.schedule { @protocol.on_close(connection) }
end
end
end

if protocol.respond_to?(:on_shutdown)
def on_shutdown(connection)
@protocol.on_shutdown(connection)
end
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

# @private
def self.debug_log
if Rage.logger.debug?
Rage.logger.tagged("cable") { Rage.logger.debug { yield } }
end
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 484e7d2

Please sign in to comment.