anycable 0.6.3 → 1.0.0.rc1

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.
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anycable/rpc/rpc_pb"
4
+ require "anycable/rpc/rpc_services_pb"
5
+
6
+ # Extend some PB auto-generated classes
7
+ module AnyCable
8
+ # Current RPC proto version (used for compatibility checks)
9
+ PROTO_VERSION = "v1"
10
+ SESSION_KEY = "_s_"
11
+
12
+ # Add setters/getter for cstate field
13
+ module WithConnectionState
14
+ def initialize(session: nil, **other)
15
+ if session
16
+ other[:cstate] ||= {}
17
+ other[:cstate][SESSION_KEY] = session
18
+ end
19
+ super(**other)
20
+ end
21
+
22
+ def session=(val)
23
+ self.cstate = {} unless cstate
24
+ cstate[SESSION_KEY] = val
25
+ end
26
+
27
+ def session
28
+ cstate[SESSION_KEY]
29
+ end
30
+
31
+ def cstate
32
+ env.cstate
33
+ end
34
+
35
+ def cstate=(val)
36
+ env.cstate = val
37
+ end
38
+
39
+ def istate
40
+ env.istate
41
+ end
42
+
43
+ def istate=(val)
44
+ env.istate = val
45
+ end
46
+ end
47
+
48
+ # Status predicates
49
+ module StatusPredicates
50
+ def success?
51
+ status == :SUCCESS
52
+ end
53
+
54
+ def failure?
55
+ status == :FAILURE
56
+ end
57
+
58
+ def error?
59
+ status == :ERROR
60
+ end
61
+ end
62
+
63
+ class ConnectionResponse
64
+ prepend WithConnectionState
65
+ include StatusPredicates
66
+ end
67
+
68
+ class CommandMessage
69
+ prepend WithConnectionState
70
+ end
71
+
72
+ class CommandResponse
73
+ prepend WithConnectionState
74
+ include StatusPredicates
75
+ end
76
+
77
+ class DisconnectRequest
78
+ prepend WithConnectionState
79
+ end
80
+
81
+ class DisconnectResponse
82
+ include StatusPredicates
83
+ end
84
+ end
@@ -1,51 +1,69 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Generated by the protocol buffer compiler. DO NOT EDIT!
2
4
  # source: rpc.proto
3
5
 
4
- require 'google/protobuf'
6
+ require "google/protobuf"
5
7
 
6
8
  Google::Protobuf::DescriptorPool.generated_pool.build do
7
- add_message "anycable.ConnectionRequest" do
8
- optional :path, :string, 1
9
- map :headers, :string, :string, 2
10
- end
11
- add_message "anycable.ConnectionResponse" do
12
- optional :status, :enum, 1, "anycable.Status"
13
- optional :identifiers, :string, 2
14
- repeated :transmissions, :string, 3
15
- optional :error_msg, :string, 4
16
- end
17
- add_message "anycable.CommandMessage" do
18
- optional :command, :string, 1
19
- optional :identifier, :string, 2
20
- optional :connection_identifiers, :string, 3
21
- optional :data, :string, 4
22
- end
23
- add_message "anycable.CommandResponse" do
24
- optional :status, :enum, 1, "anycable.Status"
25
- optional :disconnect, :bool, 2
26
- optional :stop_streams, :bool, 3
27
- repeated :streams, :string, 4
28
- repeated :transmissions, :string, 5
29
- optional :error_msg, :string, 6
30
- end
31
- add_message "anycable.DisconnectRequest" do
32
- optional :identifiers, :string, 1
33
- repeated :subscriptions, :string, 2
34
- optional :path, :string, 3
35
- map :headers, :string, :string, 4
36
- end
37
- add_message "anycable.DisconnectResponse" do
38
- optional :status, :enum, 1, "anycable.Status"
39
- optional :error_msg, :string, 2
40
- end
41
- add_enum "anycable.Status" do
42
- value :ERROR, 0
43
- value :SUCCESS, 1
44
- value :FAILURE, 2
9
+ add_file("rpc.proto", syntax: :proto3) do
10
+ add_message "anycable.Env" do
11
+ optional :url, :string, 1
12
+ map :headers, :string, :string, 2
13
+ map :cstate, :string, :string, 3
14
+ map :istate, :string, :string, 4
15
+ end
16
+ add_message "anycable.EnvResponse" do
17
+ map :cstate, :string, :string, 1
18
+ map :istate, :string, :string, 2
19
+ end
20
+ add_message "anycable.ConnectionRequest" do
21
+ optional :env, :message, 3, "anycable.Env"
22
+ end
23
+ add_message "anycable.ConnectionResponse" do
24
+ optional :status, :enum, 1, "anycable.Status"
25
+ optional :identifiers, :string, 2
26
+ repeated :transmissions, :string, 3
27
+ optional :error_msg, :string, 4
28
+ optional :env, :message, 5, "anycable.EnvResponse"
29
+ end
30
+ add_message "anycable.CommandMessage" do
31
+ optional :command, :string, 1
32
+ optional :identifier, :string, 2
33
+ optional :connection_identifiers, :string, 3
34
+ optional :data, :string, 4
35
+ optional :env, :message, 5, "anycable.Env"
36
+ end
37
+ add_message "anycable.CommandResponse" do
38
+ optional :status, :enum, 1, "anycable.Status"
39
+ optional :disconnect, :bool, 2
40
+ optional :stop_streams, :bool, 3
41
+ repeated :streams, :string, 4
42
+ repeated :transmissions, :string, 5
43
+ optional :error_msg, :string, 6
44
+ optional :env, :message, 7, "anycable.EnvResponse"
45
+ repeated :stopped_streams, :string, 8
46
+ end
47
+ add_message "anycable.DisconnectRequest" do
48
+ optional :identifiers, :string, 1
49
+ repeated :subscriptions, :string, 2
50
+ optional :env, :message, 5, "anycable.Env"
51
+ end
52
+ add_message "anycable.DisconnectResponse" do
53
+ optional :status, :enum, 1, "anycable.Status"
54
+ optional :error_msg, :string, 2
55
+ end
56
+ add_enum "anycable.Status" do
57
+ value :ERROR, 0
58
+ value :SUCCESS, 1
59
+ value :FAILURE, 2
60
+ end
45
61
  end
46
62
  end
47
63
 
48
64
  module AnyCable
65
+ Env = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.Env").msgclass
66
+ EnvResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.EnvResponse").msgclass
49
67
  ConnectionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionRequest").msgclass
50
68
  ConnectionResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionResponse").msgclass
51
69
  CommandMessage = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.CommandMessage").msgclass
@@ -1,17 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Generated by the protocol buffer compiler. DO NOT EDIT!
2
4
  # Source: rpc.proto for package 'anycable'
3
5
 
4
- require 'grpc'
6
+ require "grpc"
5
7
 
6
8
  module AnyCable
7
9
  module RPC
8
10
  class Service
9
-
10
11
  include GRPC::GenericService
11
12
 
12
13
  self.marshal_class_method = :encode
13
14
  self.unmarshal_class_method = :decode
14
- self.service_name = 'anycable.RPC'
15
+ self.service_name = "anycable.RPC"
15
16
 
16
17
  rpc :Connect, ConnectionRequest, ConnectionResponse
17
18
  rpc :Command, CommandMessage, CommandResponse
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "anycable/socket"
4
- require "anycable/rpc/rpc_pb"
5
- require "anycable/rpc/rpc_services_pb"
4
+ require "anycable/rpc"
6
5
 
7
6
  # rubocop:disable Metrics/AbcSize
8
7
  # rubocop:disable Metrics/MethodLength
@@ -14,22 +13,26 @@ module AnyCable
14
13
  def connect(request, _unused_call)
15
14
  logger.debug("RPC Connect: #{request.inspect}")
16
15
 
17
- socket = build_socket(env: rack_env(request))
16
+ socket = build_socket(env: rack_env(request.env))
18
17
 
19
18
  connection = factory.call(socket)
20
19
 
21
20
  connection.handle_open
22
21
 
23
22
  if socket.closed?
24
- AnyCable::ConnectionResponse.new(status: AnyCable::Status::FAILURE)
23
+ AnyCable::ConnectionResponse.new(
24
+ status: AnyCable::Status::FAILURE,
25
+ transmissions: socket.transmissions
26
+ )
25
27
  else
26
28
  AnyCable::ConnectionResponse.new(
27
29
  status: AnyCable::Status::SUCCESS,
28
30
  identifiers: connection.identifiers_json,
29
- transmissions: socket.transmissions
31
+ transmissions: socket.transmissions,
32
+ env: build_env_response(socket)
30
33
  )
31
34
  end
32
- rescue StandardError => exp
35
+ rescue => exp
33
36
  notify_exception(exp, :connect, request)
34
37
 
35
38
  AnyCable::ConnectionResponse.new(
@@ -41,7 +44,7 @@ module AnyCable
41
44
  def disconnect(request, _unused_call)
42
45
  logger.debug("RPC Disconnect: #{request.inspect}")
43
46
 
44
- socket = build_socket(env: rack_env(request))
47
+ socket = build_socket(env: rack_env(request.env))
45
48
 
46
49
  connection = factory.call(
47
50
  socket,
@@ -54,7 +57,7 @@ module AnyCable
54
57
  else
55
58
  AnyCable::DisconnectResponse.new(status: AnyCable::Status::FAILURE)
56
59
  end
57
- rescue StandardError => exp
60
+ rescue => exp
58
61
  notify_exception(exp, :disconnect, request)
59
62
 
60
63
  AnyCable::DisconnectResponse.new(
@@ -66,7 +69,7 @@ module AnyCable
66
69
  def command(message, _unused_call)
67
70
  logger.debug("RPC Command: #{message.inspect}")
68
71
 
69
- socket = build_socket
72
+ socket = build_socket(env: rack_env(message.env))
70
73
 
71
74
  connection = factory.call(
72
75
  socket,
@@ -83,10 +86,12 @@ module AnyCable
83
86
  status: result ? AnyCable::Status::SUCCESS : AnyCable::Status::FAILURE,
84
87
  disconnect: socket.closed?,
85
88
  stop_streams: socket.stop_streams?,
86
- streams: socket.streams,
87
- transmissions: socket.transmissions
89
+ streams: socket.streams[:start],
90
+ stopped_streams: socket.streams[:stop],
91
+ transmissions: socket.transmissions,
92
+ env: build_env_response(socket)
88
93
  )
89
- rescue StandardError => exp
94
+ rescue => exp
90
95
  notify_exception(exp, :command, message)
91
96
 
92
97
  AnyCable::CommandResponse.new(
@@ -97,24 +102,52 @@ module AnyCable
97
102
 
98
103
  private
99
104
 
100
- # Build env from path
101
- def rack_env(request)
102
- uri = URI.parse(request.path)
103
- {
104
- "QUERY_STRING" => uri.query,
105
- "SCRIPT_NAME" => "",
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!({
106
111
  "PATH_INFO" => uri.path,
107
- "SERVER_PORT" => uri.port.to_s,
112
+ "QUERY_STRING" => uri.query,
113
+ "SERVER_NAME" => uri.host,
114
+ "SERVER_PORT" => uri.port,
108
115
  "HTTP_HOST" => uri.host,
109
- # Hack to avoid Missing rack.input error
110
- "rack.request.form_input" => "",
111
- "rack.input" => "",
112
- "rack.request.form_hash" => {}
113
- }.merge(build_headers(request.headers))
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
+ }
114
147
  end
115
148
 
116
149
  def build_socket(**options)
117
- AnyCable::Socket.new(options)
150
+ AnyCable::Socket.new(**options)
118
151
  end
119
152
 
120
153
  def build_headers(headers)
@@ -125,6 +158,13 @@ module AnyCable
125
158
  end
126
159
  end
127
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
+
128
168
  def logger
129
169
  AnyCable.logger
130
170
  end
@@ -0,0 +1,6 @@
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"
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,13 @@
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
@@ -0,0 +1,15 @@
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
+ end
13
+
14
+ after(:all) { @server.stop }
15
+ end
@@ -21,51 +21,9 @@ module AnyCable
21
21
  # # stop server
22
22
  # server.stop
23
23
  class Server
24
- class << self
25
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
26
- def start(**options)
27
- warn <<~DEPRECATION
28
- DEPRECATION WARNING: Using AnyCable::Server.start is deprecated!
29
- Please, use anycable CLI instead.
30
-
31
- See https://docs.anycable.io/#upgrade_to_0_6_0
32
- DEPRECATION
33
-
34
- AnyCable.server_callbacks.each(&:call)
35
-
36
- server = new(
37
- host: AnyCable.config.rpc_host,
38
- **AnyCable.config.to_grpc_params,
39
- interceptors: AnyCable.middleware.to_a,
40
- **options
41
- )
42
-
43
- AnyCable.middleware.freeze
44
-
45
- if AnyCable.config.http_health_port_provided?
46
- health_server = AnyCable::HealthServer.new(
47
- server,
48
- **AnyCable.config.to_http_health_params
49
- )
50
- health_server.start
51
- end
52
-
53
- at_exit do
54
- server.stop
55
- health_server&.stop
56
- end
57
-
58
- AnyCable.logger.info "Broadcasting Redis channel: #{AnyCable.config.redis_channel}"
59
-
60
- server.start
61
- server.wait_till_terminated
62
- end
63
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
64
- end
65
-
66
24
  attr_reader :grpc_server, :host
67
25
 
68
- def initialize(host:, logger: AnyCable.logger, **options)
26
+ def initialize(host:, logger: nil, **options)
69
27
  @logger = logger
70
28
  @host = host
71
29
  @grpc_server = build_server(options)
@@ -78,8 +36,6 @@ module AnyCable
78
36
 
79
37
  raise "Cannot re-start stopped server" if stopped?
80
38
 
81
- check_default_host
82
-
83
39
  logger.info "RPC server is starting..."
84
40
 
85
41
  @start_thread = Thread.new { grpc_server.run }
@@ -114,10 +70,14 @@ module AnyCable
114
70
 
115
71
  private
116
72
 
117
- attr_reader :logger, :start_thread
73
+ attr_reader :start_thread
74
+
75
+ def logger
76
+ @logger ||= AnyCable.logger
77
+ end
118
78
 
119
79
  def build_server(options)
120
- GRPC::RpcServer.new(options).tap do |server|
80
+ GRPC::RpcServer.new(**options).tap do |server|
121
81
  server.add_http2_port(host, :this_port_is_insecure)
122
82
  server.handle(AnyCable::RPCHandler)
123
83
  server.handle(build_health_checker)
@@ -132,21 +92,5 @@ module AnyCable
132
92
  )
133
93
  health_checker
134
94
  end
135
-
136
- def check_default_host
137
- return unless host.is_a?(Anycable::Config::DefaultHostWrapper)
138
-
139
- warn <<~DEPRECATION
140
- DEPRECATION WARNING: You're using default rpc_host configuration which starts AnyCable RPC
141
- server on all available interfaces including external IPv4 and IPv6.
142
- This is about to be changed to loopback interface only in future versions.
143
-
144
- Please, consider switching to the loopback interface or set "[::]:50051"
145
- explicitly in your configuration, if you want to continue with the current
146
- behavior and supress this message.
147
-
148
- See https://docs.anycable.io/#/configuration
149
- DEPRECATION
150
- end
151
95
  end
152
96
  end