anycable 1.0.3 → 1.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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