anycable 0.6.5 → 1.0.0.preview1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb889c8917f8196bd4323767a9d73774a47d9d73d5b76ce98b601082df456d88
4
- data.tar.gz: 64946890d603cb9395863d93bb146d7a15011062fab46e2ee5a1d6e929d93880
3
+ metadata.gz: 65d07fe802d850f89351ed8c054d2cba9c393b36f7a5aa318183362f09d53e58
4
+ data.tar.gz: b4167ba77ce930ddcb77a9e38d205215fb3ec085a93bfd3876f2bf8a5e669373
5
5
  SHA512:
6
- metadata.gz: bffef6466c0368d6950a2a46d5b80840c20954c097cbb05cbfab2a31d578d5ded652d23d612fd47806d1e0720d6e0c4f1fd986c5051c21f35bf39e3660f3d32e
7
- data.tar.gz: f738b1c94a96aada66bcfd40e51534c7d3917e209f05f3c5f01bd80afa1300ee51973e8b2bf1524a3260481549fc2462458f8d68e97d0eb34b30599dde626bf7
6
+ metadata.gz: 456af4dd28de6d32a8436ce8702ddb3ba5523b25b531bce8dac00c5bf6cd181e79c57e4d4a89d1a2c1ff3e21eda1108be3f023c65edcf1bbf5790594216ae971
7
+ data.tar.gz: b25fa2a291a6cd6bbf4734c9c59dcf991dead19c0e25c004ac696ec524e6de63bb5ea4251f8d06ce1d21699a44e178a7e445bfedcd4d70179535c6648ed67854
@@ -1,14 +1,30 @@
1
1
  # Change log
2
2
 
3
- ## master
3
+ ## 🚧 1.0.0 (_coming soon_)
4
4
 
5
- ## 0.6.5 (2020-04-01)
5
+ - **RPC schema has changed**. ([@palkan][])
6
6
 
7
- - Relax `anyway_config` dependency. ([@palkan][])
7
+ Using `anycable-go` v1.x is required.
8
+
9
+ - **Ruby 2.5+ is required**. ([@palkan][])
10
+
11
+ - Added RPC proto version check. ([@palkan][])
12
+
13
+ Server must sent `protov` metadata with the supported versions (comma-separated list). If there is no matching version an exception is raised.
14
+
15
+ Current RPC proto version is **v1**.
16
+
17
+ - Added `request` support to channels. ([@palkan][])
18
+
19
+ Now you can access `request` object in channels, too (e.g., to read headers/cookies/URL/etc).
20
+
21
+ - Change default server address from `[::]:50051` to `127.0.0.1:50051`. ([@palkan][])
22
+
23
+ See [#71](https://github.com/anycable/anycable/pull/71).
8
24
 
9
25
  ## 0.6.4 (2020-01-24)
10
26
 
11
- - Fix Ruby 2.7 warnings. ([@palkan][])
27
+ - Fix Ruby 2.7 warnings. ([@palkan])
12
28
 
13
29
  – Add `REMOTE_ADDR` socket env variable using a synthetic header passed from a websocket
14
30
  server. ([@sponomarev][])
@@ -84,7 +100,7 @@ AnyCable allows you to use custom broadcasting adapters (Redis is used by defaul
84
100
  ```ruby
85
101
  # Specify by name (tries to load `AnyCable::BroadcastAdapters::MyAdapter` from
86
102
  # "anycable/broadcast_adapters/my_adapter")
87
- AnyCable.broadcast_adapter = :my_adapter, { option: "value" }
103
+ AnyCable.broadcast_adapter = :my_adapter, {option: "value"}
88
104
  # or provide an instance (should respond_to #broadcast)
89
105
  AnyCable.broadcast_adapter = MyAdapter.new
90
106
  ```
data/README.md CHANGED
@@ -16,7 +16,7 @@ AnyCable uses the same protocol as ActionCable, so you can use its [JavaScript c
16
16
 
17
17
  ## Requirements
18
18
 
19
- - Ruby >= 2.4
19
+ - Ruby >= 2.5
20
20
  - Redis (for broadcasting, [discuss other options](https://github.com/anycable/anycable/issues/2) with us!)
21
21
 
22
22
  ## Usage
@@ -52,27 +52,27 @@ Check out our 📑 [Documentation](https://docs.anycable.io).
52
52
 
53
53
  - Install required GRPC gems:
54
54
 
55
- ```
55
+ ```sh
56
56
  gem install grpc
57
57
  gem install grpc-tools
58
58
  ```
59
59
 
60
60
  - Re-generate GRPC files (if necessary):
61
61
 
62
- ```
62
+ ```sh
63
63
  make
64
64
  ```
65
65
 
66
66
  ## Contributing
67
67
 
68
- Bug reports and pull requests are welcome on GitHub at https://github.com/anycable/anycable.
68
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/anycable/anycable](https://github.com/anycable/anycable).
69
69
 
70
70
  Please, provide reproduction script (using [this template](https://github.com/anycable/anycable/blob/master/etc/bug_report_template.rb)) when submitting bugs if possible.
71
71
 
72
72
  ## License
73
+
73
74
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
74
75
 
75
76
  ## Security Contact
76
77
 
77
78
  To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
78
-
@@ -56,14 +56,6 @@ module AnyCable
56
56
  ExceptionsHandling << block
57
57
  end
58
58
 
59
- def error_handlers
60
- warn <<~DEPRECATION
61
- Using `AnyCable.error_handlers` is deprecated!
62
- Please, use `AnyCable.capture_exception` instead.
63
- DEPRECATION
64
- ExceptionsHandling
65
- end
66
-
67
59
  # Register a callback to be invoked before
68
60
  # the server starts
69
61
  def configure_server(&block)
@@ -103,8 +95,7 @@ module AnyCable
103
95
  attr_writer :middleware
104
96
  end
105
97
 
98
+ require "anycable/middlewares/check_version"
99
+
106
100
  self.middleware = MiddlewareChain.new
107
101
  end
108
-
109
- # Backward compatibility
110
- Anycable = AnyCable
@@ -17,11 +17,11 @@ module AnyCable
17
17
  # We couldn't require the adapter itself.
18
18
  if e.path == path_to_adapter
19
19
  raise e.class, "Couldn't load the '#{adapter}' broadcast adapter for AnyCable",
20
- e.backtrace
20
+ e.backtrace
21
21
  # Bubbled up from the adapter require.
22
22
  else
23
23
  raise e.class, "Error loading the '#{adapter}' broadcast adapter for AnyCable",
24
- e.backtrace
24
+ e.backtrace
25
25
  end
26
26
  end
27
27
  end
@@ -34,7 +34,7 @@ module AnyCable
34
34
  def broadcast(stream, payload)
35
35
  redis_conn.publish(
36
36
  channel,
37
- { stream: stream, data: payload }.to_json
37
+ {stream: stream, data: payload}.to_json
38
38
  )
39
39
  end
40
40
  end
@@ -36,11 +36,11 @@ module AnyCable
36
36
 
37
37
  configure_server!
38
38
 
39
- logger.info "Starting AnyCable gRPC server (pid: #{Process.pid})"
39
+ logger.info "Starting AnyCable gRPC server (pid: #{Process.pid}, workers_num: #{config.rpc_pool_size})"
40
40
 
41
41
  print_versions!
42
42
 
43
- logger.info "Serving #{defined?(::Rails) ? 'Rails ' : ''}application from #{boot_file}"
43
+ logger.info "Serving #{defined?(::Rails) ? "Rails " : ""}application from #{boot_file}"
44
44
 
45
45
  verify_connection_factory!
46
46
 
@@ -48,6 +48,8 @@ module AnyCable
48
48
 
49
49
  log_errors!
50
50
 
51
+ use_version_check! if config.version_check_enabled?
52
+
51
53
  @server = AnyCable::Server.new(
52
54
  host: config.rpc_host,
53
55
  **config.to_grpc_params,
@@ -124,7 +126,7 @@ module AnyCable
124
126
  end
125
127
 
126
128
  def print_versions!
127
- logger.info "AnyCable version: #{AnyCable::VERSION}"
129
+ logger.info "AnyCable version: #{AnyCable::VERSION} (proto_version: #{AnyCable::PROTO_VERSION})"
128
130
  logger.info "gRPC version: #{GRPC::VERSION}"
129
131
  end
130
132
 
@@ -162,6 +164,14 @@ module AnyCable
162
164
  AnyCable.server_callbacks.each(&:call)
163
165
  end
164
166
 
167
+ def use_version_check!
168
+ require "anycable/middlewares/check_version"
169
+
170
+ AnyCable.middleware.use(
171
+ AnyCable::Middlewares::CheckVersion.new(AnyCable::PROTO_VERSION)
172
+ )
173
+ end
174
+
165
175
  def start_health_server!
166
176
  @health_server = AnyCable::HealthServer.new(
167
177
  server,
@@ -8,11 +8,9 @@ module AnyCable
8
8
  class Config < Anyway::Config
9
9
  config_name :anycable
10
10
 
11
- DefaultHostWrapper = Class.new(String)
12
-
13
11
  attr_config(
14
12
  ### gRPC options
15
- rpc_host: DefaultHostWrapper.new("[::]:50051"),
13
+ rpc_host: "127.0.0.1:50051",
16
14
  # For defaults see https://github.com/grpc/grpc/blob/51f0d35509bcdaba572d422c4f856208162022de/src/ruby/lib/grpc/generic/rpc_server.rb#L186-L216
17
15
  rpc_pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
18
16
  rpc_max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
@@ -34,9 +32,14 @@ module AnyCable
34
32
 
35
33
  ### Health check options
36
34
  http_health_port: nil,
37
- http_health_path: "/health"
35
+ http_health_path: "/health",
36
+
37
+ ### Misc options
38
+ version_check_enabled: true
38
39
  )
39
40
 
41
+ alias version_check_enabled? version_check_enabled
42
+
40
43
  ignore_options :rpc_server_args
41
44
  flag_options :log_grpc, :debug
42
45
 
@@ -71,7 +74,7 @@ module AnyCable
71
74
 
72
75
  # Build Redis parameters
73
76
  def to_redis_params
74
- { url: redis_url }.tap do |params|
77
+ {url: redis_url}.tap do |params|
75
78
  next if redis_sentinels.nil?
76
79
 
77
80
  raise ArgumentError, "redis_sentinels must be an array; got #{redis_sentinels}" unless
@@ -102,7 +105,7 @@ module AnyCable
102
105
 
103
106
  raise ArgumentError, "Invalid Sentinel value: #{sentinel}" if matches.nil?
104
107
 
105
- { "host" => matches[1], "port" => matches[2].to_i }
108
+ {"host" => matches[1], "port" => matches[2].to_i}
106
109
  end
107
110
  end
108
111
  end
@@ -11,13 +11,11 @@ module AnyCable
11
11
 
12
12
  def notify(exp, method_name, message)
13
13
  handlers.each do |handler|
14
- begin
15
- handler.call(exp, method_name, message)
16
- rescue StandardError => exp
17
- AnyCable.logger.error "!!! EXCEPTION HANDLER THREW AN ERROR !!!"
18
- AnyCable.logger.error exp
19
- AnyCable.logger.error exp.backtrace.join("\n") unless exp.backtrace.nil?
20
- end
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?
21
19
  end
22
20
  end
23
21
 
@@ -7,6 +7,9 @@ module AnyCable
7
7
  # for request/response calls
8
8
  class Middleware < GRPC::Interceptor
9
9
  def request_response(request: nil, call: nil, method: nil)
10
+ # Call middlewares only for AnyCable service
11
+ return yield unless method.receiver.is_a?(AnyCable::RPCHandler)
12
+
10
13
  call(request, call, method) do
11
14
  yield
12
15
  end
@@ -48,8 +48,8 @@ module AnyCable
48
48
 
49
49
  unless middleware.is_a?(AnyCable::Middleware)
50
50
  raise ArgumentError,
51
- "AnyCable middleware must be a subclass of AnyCable::Middleware, " \
52
- "got #{middleware} instead"
51
+ "AnyCable middleware must be a subclass of AnyCable::Middleware, " \
52
+ "got #{middleware} instead"
53
53
  end
54
54
 
55
55
  middleware
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Middlewares
5
+ # Checks that RPC client version is compatibly with
6
+ # the current RPC proto version
7
+ class CheckVersion < Middleware
8
+ attr_reader :version
9
+
10
+ def initialize(version)
11
+ @version = version
12
+ end
13
+
14
+ def call(_request, call, _method)
15
+ supported_versions = call.metadata["protov"]&.split(",")
16
+ return yield if supported_versions&.include?(version)
17
+
18
+ raise GRPC::Internal,
19
+ "Incompatible AnyCable RPC client.\nCurrent server version: #{version}.\n" \
20
+ "Client supported versions: #{call.metadata["protov"] || "unknown"}."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,76 @@
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
+ end
39
+
40
+ # Status predicates
41
+ module StatusPredicates
42
+ def success?
43
+ status == :SUCCESS
44
+ end
45
+
46
+ def failure?
47
+ status == :FAILURE
48
+ end
49
+
50
+ def error?
51
+ status == :ERROR
52
+ end
53
+ end
54
+
55
+ class ConnectionResponse
56
+ prepend WithConnectionState
57
+ include StatusPredicates
58
+ end
59
+
60
+ class CommandMessage
61
+ prepend WithConnectionState
62
+ end
63
+
64
+ class CommandResponse
65
+ prepend WithConnectionState
66
+ include StatusPredicates
67
+ end
68
+
69
+ class DisconnectRequest
70
+ prepend WithConnectionState
71
+ end
72
+
73
+ class DisconnectResponse
74
+ include StatusPredicates
75
+ end
76
+ end
@@ -1,51 +1,66 @@
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
+ end
15
+ add_message "anycable.EnvResponse" do
16
+ map :cstate, :string, :string, 1
17
+ end
18
+ add_message "anycable.ConnectionRequest" do
19
+ optional :env, :message, 3, "anycable.Env"
20
+ end
21
+ add_message "anycable.ConnectionResponse" do
22
+ optional :status, :enum, 1, "anycable.Status"
23
+ optional :identifiers, :string, 2
24
+ repeated :transmissions, :string, 3
25
+ optional :error_msg, :string, 4
26
+ optional :env, :message, 5, "anycable.EnvResponse"
27
+ end
28
+ add_message "anycable.CommandMessage" do
29
+ optional :command, :string, 1
30
+ optional :identifier, :string, 2
31
+ optional :connection_identifiers, :string, 3
32
+ optional :data, :string, 4
33
+ optional :env, :message, 5, "anycable.Env"
34
+ end
35
+ add_message "anycable.CommandResponse" do
36
+ optional :status, :enum, 1, "anycable.Status"
37
+ optional :disconnect, :bool, 2
38
+ optional :stop_streams, :bool, 3
39
+ repeated :streams, :string, 4
40
+ repeated :transmissions, :string, 5
41
+ optional :error_msg, :string, 6
42
+ optional :env, :message, 7, "anycable.EnvResponse"
43
+ end
44
+ add_message "anycable.DisconnectRequest" do
45
+ optional :identifiers, :string, 1
46
+ repeated :subscriptions, :string, 2
47
+ optional :env, :message, 5, "anycable.Env"
48
+ end
49
+ add_message "anycable.DisconnectResponse" do
50
+ optional :status, :enum, 1, "anycable.Status"
51
+ optional :error_msg, :string, 2
52
+ end
53
+ add_enum "anycable.Status" do
54
+ value :ERROR, 0
55
+ value :SUCCESS, 1
56
+ value :FAILURE, 2
57
+ end
45
58
  end
46
59
  end
47
60
 
48
61
  module AnyCable
62
+ Env = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.Env").msgclass
63
+ EnvResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.EnvResponse").msgclass
49
64
  ConnectionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionRequest").msgclass
50
65
  ConnectionResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionResponse").msgclass
51
66
  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,9 +69,7 @@ module AnyCable
66
69
  def command(message, _unused_call)
67
70
  logger.debug("RPC Command: #{message.inspect}")
68
71
 
69
- # We don't have path/headers information here,
70
- # but we still want `connection.env` to work
71
- socket = build_socket(env: base_rack_env)
72
+ socket = build_socket(env: rack_env(message.env))
72
73
 
73
74
  connection = factory.call(
74
75
  socket,
@@ -86,9 +87,10 @@ module AnyCable
86
87
  disconnect: socket.closed?,
87
88
  stop_streams: socket.stop_streams?,
88
89
  streams: socket.streams,
89
- transmissions: socket.transmissions
90
+ transmissions: socket.transmissions,
91
+ env: build_env_response(socket)
90
92
  )
91
- rescue StandardError => exp
93
+ rescue => exp
92
94
  notify_exception(exp, :command, message)
93
95
 
94
96
  AnyCable::CommandResponse.new(
@@ -100,8 +102,8 @@ module AnyCable
100
102
  private
101
103
 
102
104
  # Build Rack env from request
103
- def rack_env(request)
104
- uri = URI.parse(request.path)
105
+ def rack_env(request_env)
106
+ uri = URI.parse(request_env.url)
105
107
 
106
108
  env = base_rack_env
107
109
  env.merge!(
@@ -110,11 +112,13 @@ module AnyCable
110
112
  "SERVER_NAME" => uri.host,
111
113
  "SERVER_PORT" => uri.port.to_s,
112
114
  "HTTP_HOST" => uri.host,
113
- "REMOTE_ADDR" => request.headers.delete("REMOTE_ADDR"),
114
- "rack.url_scheme" => uri.scheme
115
+ "REMOTE_ADDR" => request_env.headers.delete("REMOTE_ADDR"),
116
+ "rack.url_scheme" => uri.scheme,
117
+ # AnyCable specific fields
118
+ "anycable.raw_cstate" => request_env.cstate&.to_h
115
119
  )
116
120
 
117
- env.merge!(build_headers(request.headers))
121
+ env.merge!(build_headers(request_env.headers))
118
122
  end
119
123
 
120
124
  def base_rack_env
@@ -145,6 +149,12 @@ module AnyCable
145
149
  end
146
150
  end
147
151
 
152
+ def build_env_response(socket)
153
+ AnyCable::EnvResponse.new(
154
+ cstate: socket.cstate.changed_fields
155
+ )
156
+ end
157
+
148
158
  def logger
149
159
  AnyCable.logger
150
160
  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) { "example.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,48 +21,6 @@ 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
26
  def initialize(host:, logger: AnyCable.logger, **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 }
@@ -132,21 +88,5 @@ module AnyCable
132
88
  )
133
89
  health_checker
134
90
  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
91
  end
152
92
  end
@@ -3,11 +3,41 @@
3
3
  module AnyCable
4
4
  # Socket mock to be used with application connection
5
5
  class Socket
6
- attr_reader :transmissions, :env
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
+ def write(key, val)
21
+ return if source&.[](key) == val
22
+
23
+ @source ||= {}
24
+ @dirty_keys ||= []
25
+ dirty_keys << key
26
+ source[key] = val
27
+ end
28
+
29
+ def changed_fields
30
+ return unless source && dirty_keys
31
+ source.slice(*dirty_keys)
32
+ end
33
+ end
34
+
35
+ attr_reader :transmissions, :env, :cstate
7
36
 
8
37
  def initialize(env: nil)
9
38
  @transmissions = []
10
39
  @env = env
40
+ @cstate = env["anycable.cstate"] = State.new(env["anycable.raw_cstate"])
11
41
  end
12
42
 
13
43
  def transmit(websocket_message)
@@ -32,7 +62,6 @@ module AnyCable
32
62
 
33
63
  def close
34
64
  @closed = true
35
- @transmissions.clear
36
65
  @streams&.clear
37
66
  @stop_all_streams = true
38
67
  end
@@ -44,5 +73,13 @@ module AnyCable
44
73
  def stop_streams?
45
74
  @stop_all_streams == true
46
75
  end
76
+
77
+ def session
78
+ cstate.read(SESSION_KEY)
79
+ end
80
+
81
+ def session=(val)
82
+ cstate.write(SESSION_KEY, val)
83
+ end
47
84
  end
48
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AnyCable
4
- VERSION = "0.6.5"
4
+ VERSION = "1.0.0.preview1"
5
5
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anycable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 1.0.0.preview1
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-01 00:00:00.000000000 Z
11
+ date: 2020-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anyway_config
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.4.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.4.2
27
27
  - !ruby/object:Gem::Dependency
@@ -109,19 +109,19 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '3.5'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rubocop
112
+ name: rubocop-md
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.68.0
117
+ version: '0.3'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.68.0
124
+ version: '0.3'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -137,19 +137,19 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.3.8
139
139
  - !ruby/object:Gem::Dependency
140
- name: pry-byebug
140
+ name: standard
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ">="
143
+ - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '0'
145
+ version: 0.1.7
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ">="
150
+ - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '0'
152
+ version: 0.1.7
153
153
  description: AnyCable is a polyglot replacement for ActionCable-compatible servers
154
154
  email:
155
155
  - dementiev.vm@gmail.com
@@ -175,9 +175,15 @@ files:
175
175
  - lib/anycable/health_server.rb
176
176
  - lib/anycable/middleware.rb
177
177
  - lib/anycable/middleware_chain.rb
178
+ - lib/anycable/middlewares/check_version.rb
179
+ - lib/anycable/rpc.rb
178
180
  - lib/anycable/rpc/rpc_pb.rb
179
181
  - lib/anycable/rpc/rpc_services_pb.rb
180
182
  - lib/anycable/rpc_handler.rb
183
+ - lib/anycable/rspec.rb
184
+ - lib/anycable/rspec/rpc_command_context.rb
185
+ - lib/anycable/rspec/rpc_stub_context.rb
186
+ - lib/anycable/rspec/with_grpc_server.rb
181
187
  - lib/anycable/server.rb
182
188
  - lib/anycable/socket.rb
183
189
  - lib/anycable/version.rb
@@ -198,12 +204,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
204
  requirements:
199
205
  - - ">="
200
206
  - !ruby/object:Gem::Version
201
- version: 2.4.0
207
+ version: 2.5.0
202
208
  required_rubygems_version: !ruby/object:Gem::Requirement
203
209
  requirements:
204
- - - ">="
210
+ - - ">"
205
211
  - !ruby/object:Gem::Version
206
- version: '0'
212
+ version: 1.3.1
207
213
  requirements: []
208
214
  rubygems_version: 3.0.6
209
215
  signing_key: