anycable 1.0.3 → 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.
@@ -1,24 +0,0 @@
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 RPC
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, ConnectionRequest, ConnectionResponse
18
- rpc :Command, CommandMessage, CommandResponse
19
- rpc :Disconnect, DisconnectRequest, DisconnectResponse
20
- end
21
-
22
- Stub = Service.rpc_stub_class
23
- end
24
- end
@@ -1,183 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "anycable/socket"
4
- require "anycable/rpc"
5
-
6
- # rubocop:disable Metrics/AbcSize
7
- # rubocop:disable Metrics/MethodLength
8
- # rubocop:disable Metrics/ClassLength
9
- module AnyCable
10
- # RPC service handler
11
- class RPCHandler < AnyCable::RPC::Service
12
- # Handle connection request from WebSocket server
13
- def connect(request, _unused_call)
14
- logger.debug("RPC Connect: #{request.inspect}")
15
-
16
- socket = build_socket(env: rack_env(request.env))
17
-
18
- connection = factory.call(socket)
19
-
20
- connection.handle_open
21
-
22
- if socket.closed?
23
- AnyCable::ConnectionResponse.new(
24
- status: AnyCable::Status::FAILURE,
25
- transmissions: socket.transmissions
26
- )
27
- else
28
- AnyCable::ConnectionResponse.new(
29
- status: AnyCable::Status::SUCCESS,
30
- identifiers: connection.identifiers_json,
31
- transmissions: socket.transmissions,
32
- env: build_env_response(socket)
33
- )
34
- end
35
- rescue => exp
36
- notify_exception(exp, :connect, request)
37
-
38
- AnyCable::ConnectionResponse.new(
39
- status: AnyCable::Status::ERROR,
40
- error_msg: exp.message
41
- )
42
- end
43
-
44
- def disconnect(request, _unused_call)
45
- logger.debug("RPC Disconnect: #{request.inspect}")
46
-
47
- socket = build_socket(env: rack_env(request.env))
48
-
49
- connection = factory.call(
50
- socket,
51
- identifiers: request.identifiers,
52
- subscriptions: request.subscriptions
53
- )
54
-
55
- if connection.handle_close
56
- AnyCable::DisconnectResponse.new(status: AnyCable::Status::SUCCESS)
57
- else
58
- AnyCable::DisconnectResponse.new(status: AnyCable::Status::FAILURE)
59
- end
60
- rescue => exp
61
- notify_exception(exp, :disconnect, request)
62
-
63
- AnyCable::DisconnectResponse.new(
64
- status: AnyCable::Status::ERROR,
65
- error_msg: exp.message
66
- )
67
- end
68
-
69
- def command(message, _unused_call)
70
- logger.debug("RPC Command: #{message.inspect}")
71
-
72
- socket = build_socket(env: rack_env(message.env))
73
-
74
- connection = factory.call(
75
- socket,
76
- identifiers: message.connection_identifiers
77
- )
78
-
79
- result = connection.handle_channel_command(
80
- message.identifier,
81
- message.command,
82
- message.data
83
- )
84
-
85
- AnyCable::CommandResponse.new(
86
- status: result ? AnyCable::Status::SUCCESS : AnyCable::Status::FAILURE,
87
- disconnect: socket.closed?,
88
- stop_streams: socket.stop_streams?,
89
- streams: socket.streams[:start],
90
- stopped_streams: socket.streams[:stop],
91
- transmissions: socket.transmissions,
92
- env: build_env_response(socket)
93
- )
94
- rescue => exp
95
- notify_exception(exp, :command, message)
96
-
97
- AnyCable::CommandResponse.new(
98
- status: AnyCable::Status::ERROR,
99
- error_msg: exp.message
100
- )
101
- end
102
-
103
- private
104
-
105
- # Build Rack env from request
106
- def rack_env(request_env)
107
- uri = URI.parse(request_env.url)
108
-
109
- env = base_rack_env
110
- env.merge!({
111
- "PATH_INFO" => uri.path,
112
- "QUERY_STRING" => uri.query,
113
- "SERVER_NAME" => uri.host,
114
- "SERVER_PORT" => uri.port,
115
- "HTTP_HOST" => uri.host,
116
- "REMOTE_ADDR" => request_env.headers.delete("REMOTE_ADDR"),
117
- "rack.url_scheme" => uri.scheme&.sub(/^ws/, "http"),
118
- # AnyCable specific fields
119
- "anycable.raw_cstate" => request_env.cstate&.to_h,
120
- "anycable.raw_istate" => request_env.istate&.to_h
121
- }.delete_if { |_k, v| v.nil? })
122
-
123
- env.merge!(build_headers(request_env.headers))
124
- end
125
-
126
- def base_rack_env
127
- # Minimum required variables according to Rack Spec
128
- # (not all of them though, just those enough for Action Cable to work)
129
- # See https://rubydoc.info/github/rack/rack/master/file/SPEC
130
- # and https://github.com/rack/rack/blob/master/lib/rack/lint.rb
131
- {
132
- "REQUEST_METHOD" => "GET",
133
- "SCRIPT_NAME" => "",
134
- "PATH_INFO" => "/",
135
- "QUERY_STRING" => "",
136
- "SERVER_NAME" => "",
137
- "SERVER_PORT" => "80",
138
- "rack.url_scheme" => "http",
139
- "rack.input" => StringIO.new("", "r").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
140
- "rack.version" => ::Rack::VERSION,
141
- "rack.errors" => StringIO.new("").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
142
- "rack.multithread" => true,
143
- "rack.multiprocess" => false,
144
- "rack.run_once" => false,
145
- "rack.hijack?" => false
146
- }
147
- end
148
-
149
- def build_socket(**options)
150
- AnyCable::Socket.new(**options)
151
- end
152
-
153
- def build_headers(headers)
154
- headers.each_with_object({}) do |(k, v), obj|
155
- k = k.upcase
156
- k.tr!("-", "_")
157
- obj["HTTP_#{k}"] = v
158
- end
159
- end
160
-
161
- def build_env_response(socket)
162
- AnyCable::EnvResponse.new(
163
- cstate: socket.cstate.changed_fields,
164
- istate: socket.istate.changed_fields
165
- )
166
- end
167
-
168
- def logger
169
- AnyCable.logger
170
- end
171
-
172
- def factory
173
- AnyCable.connection_factory
174
- end
175
-
176
- def notify_exception(exp, method_name, message)
177
- AnyCable::ExceptionsHandling.notify(exp, method_name.to_s, message.to_h)
178
- end
179
- end
180
- end
181
- # rubocop:enable Metrics/AbcSize
182
- # rubocop:enable Metrics/MethodLength
183
- # rubocop:enable Metrics/ClassLength
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Utils for testing AnyCable and its plugins
4
- require "anycable/rspec/rpc_stub_context"
5
- require "anycable/rspec/rpc_command_context"
6
- require "anycable/rspec/with_grpc_server"
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.shared_context "anycable:rpc:command" do
4
- include_context "anycable:rpc:stub"
5
-
6
- let(:command) { "" }
7
- let(:channel_id) { "" }
8
- let(:identifiers) { {} }
9
- let(:data) { {} }
10
-
11
- let(:request) do
12
- AnyCable::CommandMessage.new(
13
- command: command,
14
- identifier: channel_id,
15
- connection_identifiers: identifiers.to_json,
16
- data: data.to_json,
17
- env: env
18
- )
19
- end
20
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.shared_context "anycable:rpc:stub" do
4
- before(:all) do
5
- @service = AnyCable::RPC::Stub.new(AnyCable.config.rpc_host, :this_channel_is_insecure)
6
- end
7
-
8
- let(:service) { @service }
9
-
10
- let(:url) { "ws://example.anycable.com/cable" }
11
- let(:headers) { {} }
12
- let(:env) { AnyCable::Env.new(url: url, headers: headers) }
13
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.shared_context "anycable:rpc:server" do
4
- before(:all) do
5
- @server = AnyCable::Server.new(
6
- host: AnyCable.config.rpc_host,
7
- **AnyCable.config.to_grpc_params,
8
- interceptors: AnyCable.middleware.to_a
9
- )
10
-
11
- @server.start
12
- sleep 0.1
13
- end
14
-
15
- after(:all) { @server.stop }
16
- end
@@ -1,96 +0,0 @@
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/rpc_handler"
8
- require "anycable/health_server"
9
-
10
- module AnyCable
11
- # Wrapper over gRPC server.
12
- #
13
- # Basic example:
14
- #
15
- # # create new server listening on the loopback interface with 50051 port
16
- # server = AnyCable::Server.new(host: "127.0.0.1:50051")
17
- #
18
- # # run gRPC server in bakground
19
- # server.start
20
- #
21
- # # stop server
22
- # server.stop
23
- class Server
24
- attr_reader :grpc_server, :host
25
-
26
- def initialize(host:, logger: nil, **options)
27
- @logger = logger
28
- @host = host
29
- @grpc_server = build_server(options)
30
- end
31
-
32
- # Start gRPC server in background and
33
- # wait untill it ready to accept connections
34
- def start
35
- return if running?
36
-
37
- raise "Cannot re-start stopped server" if stopped?
38
-
39
- logger.info "RPC server is starting..."
40
-
41
- @start_thread = Thread.new { grpc_server.run }
42
-
43
- grpc_server.wait_till_running
44
-
45
- logger.info "RPC server is listening on #{host}"
46
- end
47
-
48
- def wait_till_terminated
49
- raise "Server is not running" unless running?
50
-
51
- start_thread.join
52
- end
53
-
54
- # Stop gRPC server if it's running
55
- def stop
56
- return unless running?
57
-
58
- grpc_server.stop
59
-
60
- logger.info "RPC server stopped"
61
- end
62
-
63
- def running?
64
- grpc_server.running_state == :running
65
- end
66
-
67
- def stopped?
68
- grpc_server.running_state == :stopped
69
- end
70
-
71
- private
72
-
73
- attr_reader :start_thread
74
-
75
- def logger
76
- @logger ||= AnyCable.logger
77
- end
78
-
79
- def build_server(options)
80
- GRPC::RpcServer.new(**options).tap do |server|
81
- server.add_http2_port(host, :this_port_is_insecure)
82
- server.handle(AnyCable::RPCHandler)
83
- server.handle(build_health_checker)
84
- end
85
- end
86
-
87
- def build_health_checker
88
- health_checker = Grpc::Health::Checker.new
89
- health_checker.add_status(
90
- "anycable.RPC",
91
- Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
92
- )
93
- health_checker
94
- end
95
- end
96
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AnyCable
4
- # Socket mock to be used with application connection
5
- class Socket
6
- # Represents the per-connection store
7
- # (for example, used to keep session beetween RPC calls)
8
- class State
9
- attr_reader :dirty_keys, :source
10
-
11
- def initialize(from)
12
- @source = from
13
- @dirty_keys = nil
14
- end
15
-
16
- def read(key)
17
- source&.[](key)
18
- end
19
-
20
- alias_method :[], :read
21
-
22
- def write(key, val)
23
- return if source&.[](key) == val
24
-
25
- @source ||= {}
26
- @dirty_keys ||= []
27
- dirty_keys << key
28
- source[key] = val
29
- end
30
-
31
- alias_method :[]=, :write
32
-
33
- def changed_fields
34
- return unless source && dirty_keys
35
- source.slice(*dirty_keys)
36
- end
37
- end
38
-
39
- attr_reader :transmissions, :env, :cstate, :istate
40
-
41
- def initialize(env: nil)
42
- @transmissions = []
43
- @env = env
44
- @cstate = env["anycable.cstate"] = State.new(env["anycable.raw_cstate"])
45
- @istate = env["anycable.istate"] = State.new(env["anycable.raw_istate"])
46
- end
47
-
48
- def transmit(websocket_message)
49
- transmissions << websocket_message
50
- end
51
-
52
- def subscribe(_channel, broadcasting)
53
- streams[:start] << broadcasting
54
- end
55
-
56
- def unsubscribe(_channel, broadcasting)
57
- streams[:stop] << broadcasting
58
- end
59
-
60
- def unsubscribe_from_all(_channel)
61
- @stop_all_streams = true
62
- end
63
-
64
- def streams
65
- @streams ||= {start: [], stop: []}
66
- end
67
-
68
- def close
69
- @closed = true
70
- @streams&.clear
71
- @stop_all_streams = true
72
- end
73
-
74
- def closed?
75
- @closed == true
76
- end
77
-
78
- def stop_streams?
79
- @stop_all_streams == true
80
- end
81
-
82
- def session
83
- cstate.read(SESSION_KEY)
84
- end
85
-
86
- def session=(val)
87
- cstate.write(SESSION_KEY, val)
88
- end
89
- end
90
- end