opal-up 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/ext/up_ext/up_ext.c CHANGED
@@ -3,6 +3,7 @@
3
3
  #include <arpa/inet.h>
4
4
  #include <ruby.h>
5
5
  #include <ruby/encoding.h>
6
+ #include <signal.h>
6
7
  #include <sys/socket.h>
7
8
  #include <sys/wait.h>
8
9
  #include <unistd.h>
@@ -21,7 +22,9 @@ static VALUE cLogger;
21
22
 
22
23
  static ID at_env;
23
24
  static ID at_handler;
25
+ static ID at_instance;
24
26
  static ID at_member_id;
27
+ static ID at_members;
25
28
  static ID at_open;
26
29
  static ID at_protocol;
27
30
  static ID at_secret;
@@ -39,6 +42,7 @@ static ID id_on_drained;
39
42
  static ID id_on_message;
40
43
  static ID id_on_open;
41
44
  static ID id_port;
45
+ static ID id_publish;
42
46
 
43
47
  static rb_encoding *utf8_encoding;
44
48
  static rb_encoding *binary_encoding;
@@ -65,6 +69,10 @@ static VALUE SERVER_NAME;
65
69
  static VALUE SERVER_PORT;
66
70
  static VALUE SERVER_PROTOCOL;
67
71
 
72
+ // both used when worker of a cluster
73
+ uws_app_t *cluster_app;
74
+ struct us_listen_socket_t *cluster_socket;
75
+
68
76
  #define set_str_val(gl_name, str) \
69
77
  rb_gc_register_address(&gl_name); \
70
78
  (gl_name) = rb_enc_str_new((str), strlen((str)), binary_encoding); \
@@ -358,12 +366,23 @@ response_error:
358
366
  uws_res_end_without_body(USE_SSL, res, false);
359
367
  }
360
368
 
369
+ static void
370
+ up_server_cluster_listen_handler(struct us_listen_socket_t *listen_socket,
371
+ uws_app_listen_config_t config, void *arg) {
372
+ if (listen_socket) {
373
+ cluster_socket = listen_socket;
374
+ fprintf(stderr, "Internal Cluster communication on http://localhost:%d\n",
375
+ config.port);
376
+ }
377
+ }
378
+
361
379
  static void up_server_listen_handler(struct us_listen_socket_t *listen_socket,
362
380
  uws_app_listen_config_t config,
363
- void *user_data) {
364
- if (listen_socket)
365
- fprintf(stderr, "Server is running on http://%s:%d\n", config.host,
381
+ void *arg) {
382
+ if (listen_socket) {
383
+ fprintf(stderr, "Server is listening on http://%s:%d\n", config.host,
366
384
  config.port);
385
+ }
367
386
  }
368
387
 
369
388
  const rb_data_type_t up_client_t = {.wrap_struct_name = "Up::Client",
@@ -395,14 +414,14 @@ static VALUE up_client_pending(VALUE self) {
395
414
  return INT2FIX(0);
396
415
  }
397
416
 
398
- static void up_client_cluster_publish(server_s *s, int st, VALUE channel,
417
+ static void up_client_cluster_publish(char *scrt, int st, VALUE channel,
399
418
  VALUE message) {
400
419
  const char *opening_line = "POST " INTERNAL_PUBLISH_PATH " HTTP/1.1\r\n";
401
420
  const char *host_header = "Host: localhost\r\n";
402
421
  const char *secret = "Secret: ";
403
422
  char secret_header[50];
404
423
  memcpy(secret_header, secret, 8);
405
- memcpy(secret_header + 8, s->secret, 36);
424
+ memcpy(secret_header + 8, scrt, 36);
406
425
  memcpy(secret_header + 8 + 36, "\r\n", 2);
407
426
  const char *content_type = "Content-Type: text/plain\r\n";
408
427
  long c_length = RSTRING_LEN(channel) + RSTRING_LEN(message) + 2;
@@ -431,12 +450,25 @@ static void up_client_cluster_publish(server_s *s, int st, VALUE channel,
431
450
  // fprintf(stderr, "read: %s\n", read_buf);
432
451
  }
433
452
 
434
- static VALUE up_client_publish(int argc, VALUE *argv, VALUE self) {
453
+ static void up_internal_publish_to_member(server_s *s, VALUE channel,
454
+ VALUE message, int member_idx) {
455
+ struct sockaddr_in member_addr = {.sin_addr.s_addr = inet_addr("127.0.0.1"),
456
+ .sin_family = AF_INET};
457
+ int st = socket(AF_INET, SOCK_STREAM, 0);
458
+ if (st) {
459
+ member_addr.sin_port = htons(FIX2INT(s->port) + member_idx);
460
+ if (connect(st, (struct sockaddr *)&member_addr,
461
+ sizeof(struct sockaddr_in)) == 0) {
462
+ up_client_cluster_publish(s->secret, st, channel, message);
463
+ close(st);
464
+ }
465
+ }
466
+ }
467
+
468
+ static VALUE up_client_publish(VALUE self, VALUE channel, VALUE message) {
435
469
  uws_websocket_t *ws = DATA_PTR(self);
436
470
  if (!ws)
437
471
  return Qnil;
438
- VALUE channel, message, engine;
439
- rb_scan_args(argc, argv, "21", &channel, &message, &engine);
440
472
  if (TYPE(channel) != T_STRING)
441
473
  channel = rb_obj_as_string(channel);
442
474
  if (TYPE(message) != T_STRING)
@@ -451,20 +483,9 @@ static VALUE up_client_publish(int argc, VALUE *argv, VALUE self) {
451
483
 
452
484
  // publish to cluster members
453
485
  int i;
454
- struct sockaddr_in member_addr = {
455
- .sin_addr.s_addr = inet_addr("127.0.0.1"), .sin_family = AF_INET};
456
486
  for (i = 1; i <= s->workers; i++) {
457
- if (i != s->member_id) {
458
- int st = socket(AF_INET, SOCK_STREAM, 0);
459
- if (st) {
460
- member_addr.sin_port = htons(FIX2INT(s->port) + i);
461
- if (connect(st, (struct sockaddr *)&member_addr,
462
- sizeof(struct sockaddr_in)) == 0) {
463
- up_client_cluster_publish(s, st, channel, message);
464
- close(st);
465
- }
466
- }
467
- }
487
+ if (i != s->member_id)
488
+ up_internal_publish_to_member(s, channel, message, i);
468
489
  }
469
490
  }
470
491
  return res ? Qtrue : Qfalse;
@@ -721,9 +742,17 @@ upgrade_error:
721
742
  fprintf(stderr, "upgrade error");
722
743
  }
723
744
 
745
+ static void up_internal_close_sockets(int signal) {
746
+ if (cluster_socket)
747
+ us_listen_socket_close(false, cluster_socket);
748
+ if (cluster_app)
749
+ uws_app_close(USE_SSL, cluster_app);
750
+ }
751
+
724
752
  static VALUE up_server_listen(VALUE self) {
725
753
  server_s *s = DATA_PTR(self);
726
754
  up_internal_check_arg_types(s->rapp, &s->host, &s->port);
755
+ rb_ivar_set(mUp, at_instance, self);
727
756
 
728
757
  s->env_template = rb_hash_dup(rack_env_template);
729
758
  // When combined with SCRIPT_NAME and PATH_INFO, these variables can be used
@@ -747,8 +776,16 @@ static VALUE up_server_listen(VALUE self) {
747
776
  .port = FIX2INT(s->port), .host = RSTRING_PTR(s->host), .options = 0};
748
777
  VALUE rmember_id = rb_ivar_get(self, at_member_id);
749
778
  if (rmember_id != Qnil) {
779
+ // got a cluster
750
780
  s->member_id = FIX2INT(rmember_id);
751
- // got a cluster, open publish ports
781
+ // install signal handler
782
+ cluster_app = s->app;
783
+ cluster_socket = NULL;
784
+ struct sigaction upclcl = {.sa_handler = up_internal_close_sockets,
785
+ .sa_flags = 0};
786
+ sigemptyset(&upclcl.sa_mask);
787
+ sigaction(SIGINT, &upclcl, NULL);
788
+ // open publish ports
752
789
  VALUE rworkers = rb_ivar_get(self, at_workers);
753
790
  s->workers = FIX2INT(rworkers);
754
791
  VALUE rsecret = rb_ivar_get(self, at_secret);
@@ -761,7 +798,14 @@ static VALUE up_server_listen(VALUE self) {
761
798
  uws_app_listen_config_t config_internal = {
762
799
  .port = config.port + s->member_id, .host = "localhost", .options = 0};
763
800
  uws_app_listen_with_config(false, s->app, config_internal,
764
- up_server_listen_handler, NULL);
801
+ up_server_cluster_listen_handler, NULL);
802
+ } else {
803
+ cluster_app = s->app;
804
+ cluster_socket = NULL;
805
+ struct sigaction upclcl = {.sa_handler = up_internal_close_sockets,
806
+ .sa_flags = 0};
807
+ sigemptyset(&upclcl.sa_mask);
808
+ sigaction(SIGINT, &upclcl, NULL);
765
809
  }
766
810
  uws_app_any(USE_SSL, s->app, "/*", up_server_request_handler, (void *)s);
767
811
  uws_ws(USE_SSL, s->app, "/*",
@@ -782,6 +826,33 @@ static VALUE up_server_listen(VALUE self) {
782
826
  return self;
783
827
  }
784
828
 
829
+ static VALUE up_server_publish(VALUE self, VALUE channel, VALUE message) {
830
+ if (TYPE(channel) != T_STRING)
831
+ channel = rb_obj_as_string(channel);
832
+ if (TYPE(message) != T_STRING)
833
+ message = rb_obj_as_string(message);
834
+ server_s *s = DATA_PTR(self);
835
+ VALUE members = rb_ivar_get(self, at_members);
836
+ if (members != Qnil) {
837
+ long i, mb_cnt = RARRAY_LEN(members);
838
+ for (i = 0; i < mb_cnt; i++) {
839
+ up_internal_publish_to_member(s, channel, message, i);
840
+ }
841
+ } else {
842
+ uws_publish(USE_SSL, s->app, RSTRING_PTR(channel), RSTRING_LEN(channel),
843
+ RSTRING_PTR(message), RSTRING_LEN(message), TEXT, false);
844
+ if (s->member_id > 0) {
845
+ // publish to cluster members
846
+ int i;
847
+ for (i = 1; i <= s->workers; i++) {
848
+ if (i != s->member_id)
849
+ up_internal_publish_to_member(s, channel, message, i);
850
+ }
851
+ }
852
+ }
853
+ return Qtrue;
854
+ }
855
+
785
856
  static VALUE up_server_stop(VALUE self) {
786
857
  server_s *s = DATA_PTR(self);
787
858
  if (!s->app)
@@ -853,10 +924,19 @@ void up_setup_rack_env_template(void) {
853
924
  rb_hash_aset(rack_env_template, HTTP_VERSION, http11);
854
925
  }
855
926
 
927
+ static VALUE up_publish(VALUE self, VALUE channel, VALUE message) {
928
+ VALUE instance = rb_ivar_get(mUp, at_instance);
929
+ if (instance != Qnil)
930
+ return rb_funcall(instance, id_publish, 2, channel, message);
931
+ return Qfalse;
932
+ }
933
+
856
934
  void Init_up_ext(void) {
857
935
  at_env = rb_intern("@env");
858
936
  at_handler = rb_intern("@handler");
937
+ at_instance = rb_intern("@instance");
859
938
  at_member_id = rb_intern("@member_id");
939
+ at_members = rb_intern("@members");
860
940
  at_open = rb_intern("@open");
861
941
  at_protocol = rb_intern("@protocol");
862
942
  at_secret = rb_intern("@secret");
@@ -874,6 +954,7 @@ void Init_up_ext(void) {
874
954
  id_on_message = rb_intern("on_message");
875
955
  id_on_open = rb_intern("on_open");
876
956
  id_port = rb_intern("port");
957
+ id_publish = rb_intern("publish");
877
958
 
878
959
  utf8_encoding = rb_enc_find("UTF-8");
879
960
  binary_encoding = rb_enc_find("binary");
@@ -911,11 +992,13 @@ void Init_up_ext(void) {
911
992
  up_setup_rack_env_template();
912
993
 
913
994
  mUp = rb_define_module("Up");
995
+ rb_define_singleton_method(mUp, "publish", up_publish, 2);
996
+
914
997
  cClient = rb_define_class_under(mUp, "Client", rb_cObject);
915
998
  rb_define_alloc_func(cClient, up_client_alloc);
916
999
  rb_define_method(cClient, "close", up_client_close, 0);
917
1000
  rb_define_method(cClient, "pending", up_client_pending, 0);
918
- rb_define_method(cClient, "publish", up_client_publish, -1);
1001
+ rb_define_method(cClient, "publish", up_client_publish, 2);
919
1002
  rb_define_method(cClient, "subscribe", up_client_subscribe, -1);
920
1003
  rb_define_method(cClient, "unsubscribe", up_client_unsubscribe, -1);
921
1004
  rb_define_method(cClient, "write", up_client_write, 1);
@@ -926,5 +1009,6 @@ void Init_up_ext(void) {
926
1009
  rb_define_alloc_func(cServer, up_server_alloc);
927
1010
  rb_define_method(cServer, "initialize", up_server_init, -1);
928
1011
  rb_define_method(cServer, "listen", up_server_listen, 0);
1012
+ rb_define_method(cServer, "publish", up_server_publish, 2);
929
1013
  rb_define_method(cServer, "stop", up_server_stop, 0);
930
1014
  }
data/lib/up/bun/server.rb CHANGED
@@ -4,6 +4,13 @@ require 'up/cli'
4
4
  require 'up/client'
5
5
 
6
6
  module Up
7
+ class << self
8
+ def publish(channel, message)
9
+ raise 'no instance running' unless @instance
10
+ @instance&.publish(channel, message)
11
+ end
12
+ end
13
+
7
14
  module Bun
8
15
  class Server
9
16
  def initialize(app:, host: 'localhost', port: 3000, scheme: 'http', ca_file: nil, cert_file: nil, key_file: nil, logger: Logger.new(STDERR))
@@ -49,6 +56,7 @@ module Up
49
56
  }
50
57
  def listen
51
58
  raise "already running" if @server
59
+ ::Up.instance_variable_set(:@instance, self)
52
60
  %x{
53
61
  const oubs = Opal.Up.Bun.Server;
54
62
  const ouwc = Opal.Up.Client;
@@ -152,6 +160,15 @@ module Up
152
160
  }
153
161
  end
154
162
 
163
+ def publish(channel, message)
164
+ %x{
165
+ if (!message.$$is_string) {
166
+ message = JSON.stringify(message);
167
+ }
168
+ #@server.publish(channel, message);
169
+ }
170
+ end
171
+
155
172
  def stop
156
173
  if Up::CLI::stoppable?
157
174
  `#@server.stop()`
data/lib/up/cli.rb CHANGED
@@ -14,10 +14,10 @@ module Up
14
14
  puts self
15
15
  exit
16
16
  end
17
- on('-p', '--port PORT', String, 'Port number the server will listen to') do |port|
17
+ on('-p', '--port PORT', String, 'Port number the server will listen to. Default: 3000') do |port|
18
18
  options[:port] = port.to_i
19
19
  end
20
- on('-b', '--bind ADDRESS', String, 'Address the server will listen to') do |host|
20
+ on('-b', '--bind ADDRESS', String, 'Address the server will listen to. Default: localhost') do |host|
21
21
  options[:host] = host
22
22
  end
23
23
  on('-s', '--secure', "Use secure sockets.\nWhen using secure sockets, the -a, -c and -k options must be provided") do
@@ -32,13 +32,16 @@ module Up
32
32
  on('-k', '--key-file FILE', String, 'File with the servers certificate') do |key_file|
33
33
  options[:key_file] = key_file
34
34
  end
35
- on('-l', '--log-file FILE', String, 'log file') do |log_file|
35
+ on('-l', '--log-file FILE', String, 'Log file') do |log_file|
36
36
  options[:logger] = Logger.new(File.new(log_file, 'a+'))
37
37
  end
38
38
  on('-v', '--version', 'Show version') do
39
39
  puts "Up! v#{Up::VERSION}"
40
40
  exit
41
41
  end
42
+ on('-w', '--workers NUMBER', 'For clusters, the number of workers to run. Default: number of processors') do |workers|
43
+ options[:workers] = workers.to_i
44
+ end
42
45
  end
43
46
 
44
47
  def parse!
data/lib/up/client.rb CHANGED
@@ -36,15 +36,14 @@ module Up
36
36
  `#@ws?.getBufferedAmount()`
37
37
  end
38
38
 
39
- def publish(channel, message, engine = nil)
39
+ def publish(channel, message)
40
40
  res = false
41
- raise 'publish engine not supported' if engine
42
41
  %x{
43
42
  if (!message.$$is_string) {
44
43
  message = JSON.stringify(message);
45
44
  }
46
45
  res = #@server?.publish(channel, message);
47
- if (engine !== false && self.worker) {
46
+ if (#@worker) {
48
47
  process.send({c: channel, m: message});
49
48
  }
50
49
  }
@@ -15,24 +15,48 @@ module Up
15
15
 
16
16
  def listen
17
17
  raise "already running" unless @members.empty?
18
+ ::Up.instance_variable_set(:@instance, self)
18
19
  @workers.times do
19
20
  @members << fork do
20
21
  @member_id = @members.size + 1
21
22
  super
22
23
  end
23
24
  end
24
-
25
- Process.waitall
26
- @members.each do |member|
27
- Process.kill("KILL", member)
25
+ unless @member_id
26
+ install_signal_handlers
27
+ Process.waitall
28
28
  end
29
29
  end
30
30
 
31
31
  def stop
32
32
  if Up::CLI::stoppable?
33
- @members.each { |m| Process.kill(m) }
34
- @members.clear
33
+ kill_members
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def install_signal_handlers
40
+ Signal.trap('CHLD') do
41
+ warn "\nError: a cluster member died!"
42
+ kill_members
43
+ end
44
+ Signal.trap('INT') do
45
+ warn "\nReceived CTRL-C!"
46
+ kill_members
47
+ end
48
+ end
49
+
50
+ def kill_members
51
+ Signal.trap('CHLD', 'IGNORE')
52
+ STDERR.print "Stopping workers: "
53
+ @members.each do |mid|
54
+ Process.kill('INT', mid) rescue nil
55
+ STDERR.print '.'
35
56
  end
57
+ @members.clear
58
+ warn "\nCluster stopped."
59
+ Signal.trap('CHLD', 'DEFAULT')
36
60
  end
37
61
  end
38
62
  end
@@ -18,6 +18,7 @@ module Up
18
18
 
19
19
  def listen
20
20
  raise "already running" unless @members.empty?
21
+ ::Up.instance_variable_set(:@instance, self)
21
22
  %x{
22
23
  if (cluster.isPrimary) {
23
24
  cluster.on('message', (worker, message, handle) => {
@@ -33,9 +34,9 @@ module Up
33
34
  #@members[i] = cluster.fork();
34
35
  }
35
36
  } else {
36
- self.worker = true;
37
+ #@worker = true;
37
38
  function process_message_handler(message, handle) {
38
- self.server.publish(message.c, message.m);
39
+ #@server.publish(message.c, message.m);
39
40
  }
40
41
  process.on('message', process_message_handler);
41
42
  #{super}
@@ -43,6 +44,25 @@ module Up
43
44
  }
44
45
  end
45
46
 
47
+ def publish(channel, message)
48
+ %x{
49
+ if (!message.$$is_string) {
50
+ message = JSON.stringify(message);
51
+ }
52
+ if (#@worker ) {
53
+ #@server?.publish(channel, message);
54
+ process.send({c: channel, m: message});
55
+ } else if (#@members) {
56
+ for (let member of #@members) {
57
+ if (member !== worker) {
58
+ member.send(message);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ true
64
+ end
65
+
46
66
  def stop
47
67
  if Up::CLI::stoppable?
48
68
  @members.each { |m| `m.kill()` }
@@ -10,6 +10,13 @@ require 'up/client'
10
10
  }
11
11
 
12
12
  module Up
13
+ class << self
14
+ def publish(channel, message)
15
+ raise 'no instance running' unless @instance
16
+ @instance&.publish(channel, message)
17
+ end
18
+ end
19
+
13
20
  module UWebSocket
14
21
  class Server
15
22
  def initialize(app:, host: 'localhost', port: 3000, scheme: 'http', ca_file: nil, cert_file: nil, key_file: nil, logger: Logger.new(STDERR))
@@ -74,6 +81,7 @@ module Up
74
81
 
75
82
  def listen
76
83
  raise "already running" if @server
84
+ ::Up.instance_variable_set(:@instance, self)
77
85
  %x{
78
86
  const ouws = Opal.Up.UWebSocket.Server;
79
87
  const ouwc = Opal.Up.Client;
@@ -138,9 +146,9 @@ module Up
138
146
  client.open = false;
139
147
  client.handler = handler
140
148
  client.protocol = #{:websocket};
141
- client.server = self.server;
149
+ client.server = #@server;
142
150
  client.timeout = 120;
143
- if (self.worker) {
151
+ if (#@worker) {
144
152
  client.worker = true;
145
153
  }
146
154
  res.upgrade({ client: client },
@@ -164,6 +172,15 @@ module Up
164
172
  }
165
173
  end
166
174
 
175
+ def publish(channel, message)
176
+ %x{
177
+ if (!message.$$is_string) {
178
+ message = JSON.stringify(message);
179
+ }
180
+ #@server.publish(channel, message);
181
+ }
182
+ end
183
+
167
184
  def stop
168
185
  if Up::CLI::stoppable?
169
186
  `#@server.close()`
data/lib/up/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Up
2
- VERSION = '0.0.4'.freeze
2
+ VERSION = '0.0.6'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opal-up
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Biedermann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-12 00:00:00.000000000 Z
11
+ date: 2024-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger
@@ -58,26 +58,6 @@ dependencies:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: 3.0.9
61
- - !ruby/object:Gem::Dependency
62
- name: rackup
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: 0.2.2
68
- - - "<"
69
- - !ruby/object:Gem::Version
70
- version: 3.0.0
71
- type: :runtime
72
- prerelease: false
73
- version_requirements: !ruby/object:Gem::Requirement
74
- requirements:
75
- - - ">="
76
- - !ruby/object:Gem::Version
77
- version: 0.2.2
78
- - - "<"
79
- - !ruby/object:Gem::Version
80
- version: 3.0.0
81
61
  - !ruby/object:Gem::Dependency
82
62
  name: rake
83
63
  requirement: !ruby/object:Gem::Requirement
@@ -201,7 +181,7 @@ files:
201
181
  - lib/up/u_web_socket/server.rb
202
182
  - lib/up/u_web_socket/server_cli.rb
203
183
  - lib/up/version.rb
204
- homepage: ''
184
+ homepage: https://github.com/janbiedermann/up
205
185
  licenses:
206
186
  - MIT
207
187
  metadata: {}
@@ -223,5 +203,5 @@ requirements: []
223
203
  rubygems_version: 3.5.3
224
204
  signing_key:
225
205
  specification_version: 4
226
- summary: Rack server for Opal
206
+ summary: Rack server for Opal and Ruby
227
207
  test_files: []