anycable-core 1.1.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/CHANGELOG.md +84 -0
 - data/MIT-LICENSE +20 -0
 - data/README.md +78 -0
 - data/bin/anycable +13 -0
 - data/bin/anycabled +30 -0
 - data/bin/console +7 -0
 - data/bin/setup +6 -0
 - data/lib/anycable.rb +114 -0
 - data/lib/anycable/broadcast_adapters.rb +34 -0
 - data/lib/anycable/broadcast_adapters/base.rb +29 -0
 - data/lib/anycable/broadcast_adapters/http.rb +131 -0
 - data/lib/anycable/broadcast_adapters/redis.rb +46 -0
 - data/lib/anycable/cli.rb +319 -0
 - data/lib/anycable/config.rb +127 -0
 - data/lib/anycable/exceptions_handling.rb +35 -0
 - data/lib/anycable/grpc.rb +30 -0
 - data/lib/anycable/grpc/check_version.rb +33 -0
 - data/lib/anycable/grpc/config.rb +53 -0
 - data/lib/anycable/grpc/handler.rb +25 -0
 - data/lib/anycable/grpc/rpc_services_pb.rb +24 -0
 - data/lib/anycable/grpc/server.rb +103 -0
 - data/lib/anycable/health_server.rb +73 -0
 - data/lib/anycable/middleware.rb +10 -0
 - data/lib/anycable/middleware_chain.rb +74 -0
 - data/lib/anycable/middlewares/exceptions.rb +35 -0
 - data/lib/anycable/protos/rpc_pb.rb +74 -0
 - data/lib/anycable/rpc.rb +91 -0
 - data/lib/anycable/rpc/handler.rb +50 -0
 - data/lib/anycable/rpc/handlers/command.rb +36 -0
 - data/lib/anycable/rpc/handlers/connect.rb +33 -0
 - data/lib/anycable/rpc/handlers/disconnect.rb +27 -0
 - data/lib/anycable/rspec.rb +4 -0
 - data/lib/anycable/rspec/rpc_command_context.rb +21 -0
 - data/lib/anycable/socket.rb +169 -0
 - data/lib/anycable/version.rb +5 -0
 - data/sig/anycable.rbs +37 -0
 - data/sig/anycable/broadcast_adapters.rbs +5 -0
 - data/sig/anycable/cli.rbs +40 -0
 - data/sig/anycable/config.rbs +46 -0
 - data/sig/anycable/exceptions_handling.rbs +14 -0
 - data/sig/anycable/health_server.rbs +21 -0
 - data/sig/anycable/middleware.rbs +5 -0
 - data/sig/anycable/middleware_chain.rbs +22 -0
 - data/sig/anycable/rpc.rbs +143 -0
 - data/sig/anycable/socket.rbs +40 -0
 - data/sig/anycable/version.rbs +3 -0
 - metadata +237 -0
 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 4 
     | 
    
         
            +
              module ExceptionsHandling # :nodoc:
         
     | 
| 
      
 5 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def add_handler(block)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    handlers << procify(block)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  alias_method :<<, :add_handler
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def notify(exp, method_name, message)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    handlers.each do |handler|
         
     | 
| 
      
 14 
     | 
    
         
            +
                      handler.call(exp, method_name, message)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    rescue => exp
         
     | 
| 
      
 16 
     | 
    
         
            +
                      AnyCable.logger.error "!!! EXCEPTION HANDLER THREW AN ERROR !!!"
         
     | 
| 
      
 17 
     | 
    
         
            +
                      AnyCable.logger.error exp
         
     | 
| 
      
 18 
     | 
    
         
            +
                      AnyCable.logger.error exp.backtrace.join("\n") unless exp.backtrace.nil?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  private
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def procify(block)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    return block unless block.lambda?
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    proc { |*args| block.call(*args.take(block.arity)) }
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def handlers
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @handlers ||= []
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "grpc"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 6 
     | 
    
         
            +
              module GRPC
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            require "anycable/grpc/config"
         
     | 
| 
      
 11 
     | 
    
         
            +
            require "anycable/grpc/server"
         
     | 
| 
      
 12 
     | 
    
         
            +
            require "anycable/grpc/check_version"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            AnyCable.server_builder = ->(config) {
         
     | 
| 
      
 15 
     | 
    
         
            +
              AnyCable.logger.info "gRPC version: #{::GRPC::VERSION}"
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              ::GRPC.define_singleton_method(:logger) { AnyCable.logger } if config.log_grpc?
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              interceptors = []
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              if config.version_check_enabled?
         
     | 
| 
      
 22 
     | 
    
         
            +
                interceptors << AnyCable::GRPC::CheckVersion.new(AnyCable::PROTO_VERSION)
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              params = config.to_grpc_params
         
     | 
| 
      
 26 
     | 
    
         
            +
              params[:host] = config.rpc_host
         
     | 
| 
      
 27 
     | 
    
         
            +
              params[:interceptors] = interceptors
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              AnyCable::GRPC::Server.new(**params)
         
     | 
| 
      
 30 
     | 
    
         
            +
            }
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 4 
     | 
    
         
            +
              module GRPC
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Checks that gRPC client version is compatible with
         
     | 
| 
      
 6 
     | 
    
         
            +
                # the current RPC proto version
         
     | 
| 
      
 7 
     | 
    
         
            +
                class CheckVersion < ::GRPC::ServerInterceptor
         
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_reader :version
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(version)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @version = version
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def request_response(request: nil, call: nil, method: nil)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    # Call only for AnyCable service
         
     | 
| 
      
 16 
     | 
    
         
            +
                    return yield unless method.receiver.is_a?(AnyCable::GRPC::Handler)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    check_version(call) do
         
     | 
| 
      
 19 
     | 
    
         
            +
                      yield
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def check_version(call)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    supported_versions = call.metadata["protov"]&.split(",")
         
     | 
| 
      
 25 
     | 
    
         
            +
                    return yield if supported_versions&.include?(version)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    raise ::GRPC::Internal,
         
     | 
| 
      
 28 
     | 
    
         
            +
                      "Incompatible AnyCable RPC client.\nCurrent server version: #{version}.\n" \
         
     | 
| 
      
 29 
     | 
    
         
            +
                      "Client supported versions: #{call.metadata["protov"] || "unknown"}."
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            AnyCable::Config.attr_config(
         
     | 
| 
      
 4 
     | 
    
         
            +
              ### gRPC options
         
     | 
| 
      
 5 
     | 
    
         
            +
              rpc_host: "127.0.0.1:50051",
         
     | 
| 
      
 6 
     | 
    
         
            +
              # For defaults see https://github.com/grpc/grpc/blob/51f0d35509bcdaba572d422c4f856208162022de/src/ruby/lib/grpc/generic/rpc_server.rb#L186-L216
         
     | 
| 
      
 7 
     | 
    
         
            +
              rpc_pool_size: ::GRPC::RpcServer::DEFAULT_POOL_SIZE,
         
     | 
| 
      
 8 
     | 
    
         
            +
              rpc_max_waiting_requests: ::GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
         
     | 
| 
      
 9 
     | 
    
         
            +
              rpc_poll_period: ::GRPC::RpcServer::DEFAULT_POLL_PERIOD,
         
     | 
| 
      
 10 
     | 
    
         
            +
              rpc_pool_keep_alive: ::GRPC::Pool::DEFAULT_KEEP_ALIVE,
         
     | 
| 
      
 11 
     | 
    
         
            +
              # See https://github.com/grpc/grpc/blob/f526602bff029b8db50a8d57134d72da33d8a752/include/grpc/impl/codegen/grpc_types.h#L292-L315
         
     | 
| 
      
 12 
     | 
    
         
            +
              rpc_server_args: {},
         
     | 
| 
      
 13 
     | 
    
         
            +
              log_grpc: false
         
     | 
| 
      
 14 
     | 
    
         
            +
            )
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            AnyCable::Config.ignore_options :rpc_server_args
         
     | 
| 
      
 17 
     | 
    
         
            +
            AnyCable::Config.flag_options :log_grpc
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 20 
     | 
    
         
            +
              module GRPC
         
     | 
| 
      
 21 
     | 
    
         
            +
                module Config
         
     | 
| 
      
 22 
     | 
    
         
            +
                  def log_grpc
         
     | 
| 
      
 23 
     | 
    
         
            +
                    debug? || super
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  # Add alias explicitly, 'cause previous alias refers to the original log_grpc method
         
     | 
| 
      
 27 
     | 
    
         
            +
                  alias_method :log_grpc?, :log_grpc
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  # Build gRPC server parameters
         
     | 
| 
      
 30 
     | 
    
         
            +
                  def to_grpc_params
         
     | 
| 
      
 31 
     | 
    
         
            +
                    {
         
     | 
| 
      
 32 
     | 
    
         
            +
                      pool_size: rpc_pool_size,
         
     | 
| 
      
 33 
     | 
    
         
            +
                      max_waiting_requests: rpc_max_waiting_requests,
         
     | 
| 
      
 34 
     | 
    
         
            +
                      poll_period: rpc_poll_period,
         
     | 
| 
      
 35 
     | 
    
         
            +
                      pool_keep_alive: rpc_pool_keep_alive,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      server_args: rpc_server_args
         
     | 
| 
      
 37 
     | 
    
         
            +
                    }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            AnyCable::Config.prepend AnyCable::GRPC::Config
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            AnyCable::Config.usage <<~TXT
         
     | 
| 
      
 46 
     | 
    
         
            +
              GRPC OPTIONS
         
     | 
| 
      
 47 
     | 
    
         
            +
                  --rpc-host=host                   Local address to run gRPC server on, default: "127.0.0.1:50051"
         
     | 
| 
      
 48 
     | 
    
         
            +
                  --rpc-pool-size=size              gRPC workers pool size, default: 30
         
     | 
| 
      
 49 
     | 
    
         
            +
                  --rpc-max-waiting-requests=num    Max waiting requests queue size, default: 20
         
     | 
| 
      
 50 
     | 
    
         
            +
                  --rpc-poll-period=seconds         Poll period (sec), default: 1
         
     | 
| 
      
 51 
     | 
    
         
            +
                  --rpc-pool-keep-alive=seconds     Keep-alive polling interval (sec), default: 1
         
     | 
| 
      
 52 
     | 
    
         
            +
                  --log-grpc                        Enable gRPC logging (disabled by default)
         
     | 
| 
      
 53 
     | 
    
         
            +
            TXT
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "anycable/socket"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "anycable/rpc"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "anycable/grpc/rpc_services_pb"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 8 
     | 
    
         
            +
              module GRPC
         
     | 
| 
      
 9 
     | 
    
         
            +
                # RPC service handler
         
     | 
| 
      
 10 
     | 
    
         
            +
                class Handler < AnyCable::GRPC::Service
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # Handle connection request from WebSocket server
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def connect(request, _unused_call)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    AnyCable.rpc_handler.handle(:connect, request)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def disconnect(request, _unused_call)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    AnyCable.rpc_handler.handle(:disconnect, request)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def command(request, _unused_call)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    AnyCable.rpc_handler.handle(:command, request)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # Generated by the protocol buffer compiler.  DO NOT EDIT!
         
     | 
| 
      
 4 
     | 
    
         
            +
            # Source: rpc.proto for package 'anycable'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            require "grpc"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 9 
     | 
    
         
            +
              module GRPC
         
     | 
| 
      
 10 
     | 
    
         
            +
                class Service
         
     | 
| 
      
 11 
     | 
    
         
            +
                  include ::GRPC::GenericService
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  self.marshal_class_method = :encode
         
     | 
| 
      
 14 
     | 
    
         
            +
                  self.unmarshal_class_method = :decode
         
     | 
| 
      
 15 
     | 
    
         
            +
                  self.service_name = "anycable.RPC"
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  rpc :Connect, ::AnyCable::ConnectionRequest, ::AnyCable::ConnectionResponse
         
     | 
| 
      
 18 
     | 
    
         
            +
                  rpc :Command, ::AnyCable::CommandMessage, ::AnyCable::CommandResponse
         
     | 
| 
      
 19 
     | 
    
         
            +
                  rpc :Disconnect, ::AnyCable::DisconnectRequest, ::AnyCable::DisconnectResponse
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                Stub = Service.rpc_stub_class
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,103 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "grpc"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "grpc/health/checker"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "grpc/health/v1/health_services_pb"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            require "anycable/grpc/handler"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 10 
     | 
    
         
            +
              module GRPC
         
     | 
| 
      
 11 
     | 
    
         
            +
                using(Module.new do
         
     | 
| 
      
 12 
     | 
    
         
            +
                  refine ::GRPC::RpcServer do
         
     | 
| 
      
 13 
     | 
    
         
            +
                    attr_reader :pool_size
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Wrapper over gRPC server.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # Basic example:
         
     | 
| 
      
 20 
     | 
    
         
            +
                #
         
     | 
| 
      
 21 
     | 
    
         
            +
                #   # create new server listening on the loopback interface with 50051 port
         
     | 
| 
      
 22 
     | 
    
         
            +
                #   server = AnyCable::GRPC::Server.new(host: "127.0.0.1:50051")
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                #   # run gRPC server in bakground
         
     | 
| 
      
 25 
     | 
    
         
            +
                #   server.start
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                #   # stop server
         
     | 
| 
      
 28 
     | 
    
         
            +
                #   server.stop
         
     | 
| 
      
 29 
     | 
    
         
            +
                class Server
         
     | 
| 
      
 30 
     | 
    
         
            +
                  attr_reader :grpc_server, :host
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def initialize(host:, logger: nil, **options)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @logger = logger
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @host = host
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @grpc_server = build_server(**options)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  # Start gRPC server in background and
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # wait untill it ready to accept connections
         
     | 
| 
      
 40 
     | 
    
         
            +
                  def start
         
     | 
| 
      
 41 
     | 
    
         
            +
                    return if running?
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    raise "Cannot re-start stopped server" if stopped?
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    logger.info "RPC server is starting..."
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    @start_thread = Thread.new { grpc_server.run }
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    grpc_server.wait_till_running
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    logger.info "RPC server is listening on #{host} (workers_num: #{grpc_server.pool_size})"
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  def wait_till_terminated
         
     | 
| 
      
 55 
     | 
    
         
            +
                    raise "Server is not running" unless running?
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    start_thread.join
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  # Stop gRPC server if it's running
         
     | 
| 
      
 61 
     | 
    
         
            +
                  def stop
         
     | 
| 
      
 62 
     | 
    
         
            +
                    return unless running?
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    grpc_server.stop
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    logger.info "RPC server stopped"
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  def running?
         
     | 
| 
      
 70 
     | 
    
         
            +
                    grpc_server.running_state == :running
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def stopped?
         
     | 
| 
      
 74 
     | 
    
         
            +
                    grpc_server.running_state == :stopped
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  private
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  attr_reader :start_thread
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  def logger
         
     | 
| 
      
 82 
     | 
    
         
            +
                    @logger ||= AnyCable.logger
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def build_server(**options)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    ::GRPC::RpcServer.new(**options).tap do |server|
         
     | 
| 
      
 87 
     | 
    
         
            +
                      server.add_http2_port(host, :this_port_is_insecure)
         
     | 
| 
      
 88 
     | 
    
         
            +
                      server.handle(AnyCable::GRPC::Handler)
         
     | 
| 
      
 89 
     | 
    
         
            +
                      server.handle(build_health_checker)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
                  end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                  def build_health_checker
         
     | 
| 
      
 94 
     | 
    
         
            +
                    health_checker = Grpc::Health::Checker.new
         
     | 
| 
      
 95 
     | 
    
         
            +
                    health_checker.add_status(
         
     | 
| 
      
 96 
     | 
    
         
            +
                      "anycable.RPC",
         
     | 
| 
      
 97 
     | 
    
         
            +
                      Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
         
     | 
| 
      
 98 
     | 
    
         
            +
                    )
         
     | 
| 
      
 99 
     | 
    
         
            +
                    health_checker
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,73 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Server for HTTP healthchecks.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Basic usage:
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              #  # create a new healthcheck server for a specified
         
     | 
| 
      
 9 
     | 
    
         
            +
              #  # server listening on the port
         
     | 
| 
      
 10 
     | 
    
         
            +
              #  health_server = AnyCable::HealthServer.new(server, port)
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              #  # start health server in background
         
     | 
| 
      
 13 
     | 
    
         
            +
              #  health_server.start
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              #  # stop health server
         
     | 
| 
      
 16 
     | 
    
         
            +
              #  health_server.stop
         
     | 
| 
      
 17 
     | 
    
         
            +
              class HealthServer
         
     | 
| 
      
 18 
     | 
    
         
            +
                SUCCESS_RESPONSE = [200, "Ready"].freeze
         
     | 
| 
      
 19 
     | 
    
         
            +
                FAILURE_RESPONSE = [503, "Not Ready"].freeze
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                attr_reader :server, :port, :path, :http_server
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def initialize(server, port:, logger: nil, path: "/health")
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @server = server
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @port = port
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @path = path
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @logger = logger
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @http_server = build_server
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def start
         
     | 
| 
      
 32 
     | 
    
         
            +
                  return if running?
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  Thread.new { http_server.start }
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  logger.info "HTTP health server is listening on localhost:#{port} and mounted at \"#{path}\""
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def stop
         
     | 
| 
      
 40 
     | 
    
         
            +
                  return unless running?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  http_server.shutdown
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def running?
         
     | 
| 
      
 46 
     | 
    
         
            +
                  http_server.status == :Running
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                private
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def logger
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @logger ||= AnyCable.logger
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def build_server
         
     | 
| 
      
 56 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 57 
     | 
    
         
            +
                    require "webrick"
         
     | 
| 
      
 58 
     | 
    
         
            +
                  rescue LoadError
         
     | 
| 
      
 59 
     | 
    
         
            +
                    raise "Please, install webrick gem to use health server"
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  WEBrick::HTTPServer.new(
         
     | 
| 
      
 63 
     | 
    
         
            +
                    Port: port,
         
     | 
| 
      
 64 
     | 
    
         
            +
                    Logger: logger,
         
     | 
| 
      
 65 
     | 
    
         
            +
                    AccessLog: []
         
     | 
| 
      
 66 
     | 
    
         
            +
                  ).tap do |http_server|
         
     | 
| 
      
 67 
     | 
    
         
            +
                    http_server.mount_proc path do |_, res|
         
     | 
| 
      
 68 
     | 
    
         
            +
                      res.status, res.body = server.running? ? SUCCESS_RESPONSE : FAILURE_RESPONSE
         
     | 
| 
      
 69 
     | 
    
         
            +
                    end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,74 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "anycable/middleware"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "monitor"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module AnyCable
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Middleware chain is used to build the list of
         
     | 
| 
      
 8 
     | 
    
         
            +
              # gRPC server interceptors.
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Each interceptor should be a subsclass of
         
     | 
| 
      
 11 
     | 
    
         
            +
              # AnyCable::Middleware and implement `#call` method.
         
     | 
| 
      
 12 
     | 
    
         
            +
              class MiddlewareChain
         
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @registry = []
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @mu = Monitor.new
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def use(middleware)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  check_frozen!
         
     | 
| 
      
 20 
     | 
    
         
            +
                  middleware = build_middleware(middleware)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  sync { registry << middleware }
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def freeze
         
     | 
| 
      
 25 
     | 
    
         
            +
                  registry.freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
                  super
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def to_a
         
     | 
| 
      
 30 
     | 
    
         
            +
                  registry
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def call(method_name, request, &block)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  return yield(method_name, request) if registry.none?
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  execute_next_middleware(0, method_name, request, block)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                private
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def execute_next_middleware(ind, method_name, request, block)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  return block.call(method_name, request) if ind >= registry.size
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  registry[ind].call(method_name, request) do
         
     | 
| 
      
 45 
     | 
    
         
            +
                    execute_next_middleware(ind + 1, method_name, request, block)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                private
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                attr_reader :mu, :registry
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                def sync
         
     | 
| 
      
 54 
     | 
    
         
            +
                  mu.synchronize { yield }
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def check_frozen!
         
     | 
| 
      
 58 
     | 
    
         
            +
                  raise "Cannot modify AnyCable middlewares after server started" if frozen?
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def build_middleware(middleware)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  middleware = middleware.new if
         
     | 
| 
      
 63 
     | 
    
         
            +
                    middleware.is_a?(Class) && middleware <= AnyCable::Middleware
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  unless middleware.is_a?(AnyCable::Middleware)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    raise ArgumentError,
         
     | 
| 
      
 67 
     | 
    
         
            +
                      "AnyCable middleware must be a subclass of AnyCable::Middleware, " \
         
     | 
| 
      
 68 
     | 
    
         
            +
                      "got #{middleware} instead"
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  middleware
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     |