iodine 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/LIMITS.md +25 -0
  4. data/README.md +39 -80
  5. data/SPEC-Websocket-Draft.md +129 -4
  6. data/bin/echo +2 -2
  7. data/bin/http-hello +1 -0
  8. data/bin/updated api +113 -0
  9. data/bin/ws-echo +0 -1
  10. data/examples/broadcast.ru +56 -0
  11. data/examples/echo.ru +57 -0
  12. data/examples/hello.ru +30 -0
  13. data/examples/redis.ru +69 -0
  14. data/examples/shootout.ru +53 -0
  15. data/exe/iodine +2 -80
  16. data/ext/iodine/defer.c +11 -5
  17. data/ext/iodine/empty.h +26 -0
  18. data/ext/iodine/evio.h +1 -1
  19. data/ext/iodine/facil.c +103 -61
  20. data/ext/iodine/facil.h +20 -12
  21. data/ext/iodine/fio_dict.c +446 -0
  22. data/ext/iodine/fio_dict.h +90 -0
  23. data/ext/iodine/fio_hash_table.h +370 -0
  24. data/ext/iodine/fio_list.h +30 -3
  25. data/ext/iodine/http.c +169 -37
  26. data/ext/iodine/http.h +33 -10
  27. data/ext/iodine/http1.c +78 -42
  28. data/ext/iodine/http_request.c +6 -0
  29. data/ext/iodine/http_request.h +3 -0
  30. data/ext/iodine/http_response.c +43 -11
  31. data/ext/iodine/iodine.c +380 -0
  32. data/ext/iodine/iodine.h +62 -0
  33. data/ext/iodine/iodine_helpers.c +235 -0
  34. data/ext/iodine/iodine_helpers.h +13 -0
  35. data/ext/iodine/iodine_http.c +409 -241
  36. data/ext/iodine/iodine_http.h +7 -14
  37. data/ext/iodine/iodine_protocol.c +626 -0
  38. data/ext/iodine/iodine_protocol.h +13 -0
  39. data/ext/iodine/iodine_pubsub.c +646 -0
  40. data/ext/iodine/iodine_pubsub.h +27 -0
  41. data/ext/iodine/iodine_websockets.c +796 -0
  42. data/ext/iodine/iodine_websockets.h +19 -0
  43. data/ext/iodine/pubsub.c +544 -0
  44. data/ext/iodine/pubsub.h +215 -0
  45. data/ext/iodine/random.c +4 -4
  46. data/ext/iodine/rb-call.c +1 -5
  47. data/ext/iodine/rb-defer.c +3 -20
  48. data/ext/iodine/rb-rack-io.c +22 -22
  49. data/ext/iodine/rb-rack-io.h +3 -4
  50. data/ext/iodine/rb-registry.c +111 -118
  51. data/ext/iodine/redis_connection.c +277 -0
  52. data/ext/iodine/redis_connection.h +77 -0
  53. data/ext/iodine/redis_engine.c +398 -0
  54. data/ext/iodine/redis_engine.h +68 -0
  55. data/ext/iodine/resp.c +842 -0
  56. data/ext/iodine/resp.h +253 -0
  57. data/ext/iodine/sock.c +26 -12
  58. data/ext/iodine/sock.h +14 -3
  59. data/ext/iodine/spnlock.inc +19 -2
  60. data/ext/iodine/websockets.c +299 -11
  61. data/ext/iodine/websockets.h +159 -6
  62. data/lib/iodine.rb +104 -1
  63. data/lib/iodine/cli.rb +106 -0
  64. data/lib/iodine/monkeypatch.rb +40 -0
  65. data/lib/iodine/pubsub.rb +70 -0
  66. data/lib/iodine/version.rb +1 -1
  67. data/lib/iodine/websocket.rb +12 -0
  68. data/lib/rack/handler/iodine.rb +33 -7
  69. metadata +35 -7
  70. data/ext/iodine/iodine_core.c +0 -760
  71. data/ext/iodine/iodine_core.h +0 -79
  72. data/ext/iodine/iodine_websocket.c +0 -551
  73. data/ext/iodine/iodine_websocket.h +0 -22
  74. data/lib/iodine/http.rb +0 -4
@@ -27,6 +27,7 @@ typedef struct http1_protocol_s {
27
27
  void (*on_request)(http_request_s *request);
28
28
  struct http1_protocol_s *next;
29
29
  http1_request_s request;
30
+ ssize_t len; /* used as a persistent socket `read` indication. */
30
31
  } http1_protocol_s;
31
32
 
32
33
  static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol);
@@ -42,7 +43,7 @@ static struct {
42
43
  http1_protocol_s protocol_mem[HTTP1_POOL_SIZE];
43
44
  } http1_pool = {.lock = SPN_LOCK_INIT, .init = 0};
44
45
 
45
- static void http1_free(http1_protocol_s *pr) {
46
+ static void http1_free(intptr_t uuid, http1_protocol_s *pr) {
46
47
  if ((uintptr_t)pr < (uintptr_t)http1_pool.protocol_mem ||
47
48
  (uintptr_t)pr >= (uintptr_t)(http1_pool.protocol_mem + HTTP1_POOL_SIZE))
48
49
  goto use_free;
@@ -54,14 +55,15 @@ static void http1_free(http1_protocol_s *pr) {
54
55
  use_free:
55
56
  free(pr);
56
57
  return;
58
+ (void)uuid;
57
59
  }
58
60
 
59
61
  static inline void http1_set_protocol_data(http1_protocol_s *pr) {
60
- pr->protocol =
61
- (protocol_s){.on_data = (void (*)(intptr_t, protocol_s *))http1_on_data,
62
- .on_close = (void (*)(protocol_s *))http1_free};
62
+ pr->protocol = (protocol_s){
63
+ .on_data = (void (*)(intptr_t, protocol_s *))http1_on_data,
64
+ .on_close = (void (*)(intptr_t uuid, protocol_s *))http1_free};
63
65
  pr->request.request = (http_request_s){.fd = 0, .http_version = HTTP_V1};
64
- pr->request.header_pos = pr->request.buffer_pos = 0;
66
+ pr->request.header_pos = pr->request.buffer_pos = pr->len = 0;
65
67
  }
66
68
 
67
69
  static http1_protocol_s *http1_alloc(void) {
@@ -73,6 +75,8 @@ static http1_protocol_s *http1_alloc(void) {
73
75
  http1_pool.next = pr->next;
74
76
  spn_unlock(&http1_pool.lock);
75
77
  http1_request_clear(&pr->request.request);
78
+ pr->request.request.settings = pr->settings;
79
+ pr->len = 0;
76
80
  return pr;
77
81
  use_malloc:
78
82
  if (http1_pool.init == 0)
@@ -107,24 +111,29 @@ static void http1_on_header_found(http_request_s *request,
107
111
  ((http1_request_s *)request)->header_pos += 1;
108
112
  }
109
113
 
114
+ static void http1_on_data(intptr_t uuid, http1_protocol_s *pr);
115
+ static void http1_on_data_def(intptr_t uuid, protocol_s *pr, void *ignr) {
116
+ sock_touch(uuid);
117
+ http1_on_data(uuid, (http1_protocol_s *)pr);
118
+ (void)ignr;
119
+ }
110
120
  /* parse and call callback */
111
- static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
112
- ssize_t len = 0;
121
+ static void http1_on_data(intptr_t uuid, http1_protocol_s *pr) {
113
122
  ssize_t result;
114
123
  char buff[HTTP_BODY_CHUNK_SIZE];
115
- char *buffer;
116
- http1_request_s *request = &protocol->request;
124
+ http1_request_s *request = &pr->request;
125
+ char *buffer = request->buffer;
117
126
  for (;;) {
118
127
  // handle requests with no file data
119
128
  if (request->request.body_file <= 0) {
120
129
  // request headers parsing
121
- if (len == 0) {
130
+ if (pr->len == 0) {
122
131
  buffer = request->buffer;
123
132
  // make sure headers don't overflow
124
- len = sock_read(uuid, buffer + request->buffer_pos,
125
- HTTP1_MAX_HEADER_SIZE - request->buffer_pos);
133
+ pr->len = sock_read(uuid, buffer + request->buffer_pos,
134
+ HTTP1_MAX_HEADER_SIZE - request->buffer_pos);
126
135
  // update buffer read position.
127
- request->buffer_pos += len;
136
+ request->buffer_pos += pr->len;
128
137
  // if (len > 0) {
129
138
  // fprintf(stderr, "\n----\nRead from socket, %lu bytes, total
130
139
  // %lu:\n",
@@ -135,9 +144,8 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
135
144
  // fprintf(stderr, "\n");
136
145
  // }
137
146
  }
138
- if (len <= 0) {
139
- return;
140
- }
147
+ if (pr->len <= 0)
148
+ goto finished_reading;
141
149
 
142
150
  // parse headers
143
151
  result =
@@ -149,12 +157,11 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
149
157
  if (request->request.content_length == 0 || request->request.body_str) {
150
158
  goto handle_request;
151
159
  }
152
- if (request->request.content_length >
153
- protocol->settings->max_body_size) {
160
+ if (request->request.content_length > pr->settings->max_body_size) {
154
161
  goto body_to_big;
155
162
  }
156
163
  // initialize or submit body data
157
- result = http1_parse_request_body(buffer + result, len - result,
164
+ result = http1_parse_request_body(buffer + result, pr->len - result,
158
165
  (http_request_s *)request);
159
166
  if (result >= 0) {
160
167
  request->buffer_pos += result;
@@ -165,7 +172,7 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
165
172
  } else if (result == -1) // parser error
166
173
  goto parser_error;
167
174
  // assume incomplete (result == -2), even if wrong, we're right.
168
- len = 0;
175
+ pr->len = 0;
169
176
  continue;
170
177
  }
171
178
  if (request->request.body_file > 0) {
@@ -173,16 +180,16 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
173
180
  parse_body:
174
181
  buffer = buff;
175
182
  // request body parsing
176
- len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE);
177
- if (len <= 0)
178
- return;
179
- result = http1_parse_request_body(buffer, len, &request->request);
183
+ pr->len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE);
184
+ if (pr->len <= 0)
185
+ goto finished_reading;
186
+ result = http1_parse_request_body(buffer, pr->len, &request->request);
180
187
  if (result >= 0) {
181
188
  goto handle_request;
182
189
  } else if (result == -1) // parser error
183
190
  goto parser_error;
184
- if (len < HTTP_BODY_CHUNK_SIZE) // pause parser for more data
185
- return;
191
+ if (pr->len < HTTP_BODY_CHUNK_SIZE) // pause parser for more data
192
+ goto finished_reading;
186
193
  goto parse_body;
187
194
  }
188
195
  continue;
@@ -190,24 +197,25 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
190
197
  // review required headers / data
191
198
  if (request->request.host == NULL)
192
199
  goto bad_request;
193
- http_settings_s *settings = protocol->settings;
200
+ http_settings_s *settings = pr->settings;
201
+ request->request.settings = settings;
194
202
  // call request callback
195
- if (protocol && settings &&
203
+ if (pr && settings &&
196
204
  (request->request.upgrade || settings->public_folder == NULL ||
197
205
  http_response_sendfile2(
198
206
  NULL, &request->request, settings->public_folder,
199
207
  settings->public_folder_length, request->request.path,
200
208
  request->request.path_len, settings->log_static))) {
201
- protocol->on_request(&request->request);
209
+ pr->on_request(&request->request);
202
210
  // fprintf(stderr, "Called on_request\n");
203
211
  }
204
212
  // rotate buffer for HTTP pipelining
205
213
  if ((ssize_t)request->buffer_pos <= result) {
206
- len = 0;
214
+ pr->len = 0;
207
215
  // fprintf(stderr, "\n----\nAll data consumed.\n");
208
216
  } else {
209
217
  memmove(request->buffer, buffer + result, request->buffer_pos - result);
210
- len = request->buffer_pos - result;
218
+ pr->len = request->buffer_pos - result;
211
219
  // fprintf(stderr, "\n----\ndata after move, %lu long:\n%.*s\n", len,
212
220
  // (int)len, request->buffer);
213
221
  }
@@ -215,14 +223,22 @@ static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol) {
215
223
  // request->buffer);
216
224
  // clear request state
217
225
  http1_request_clear(&request->request);
218
- request->buffer_pos = len;
226
+ request->buffer_pos = pr->len;
219
227
  // make sure to use the correct buffer.
220
228
  buffer = request->buffer;
229
+ if (pr->len) {
230
+ /* prevent this connection from hogging the thread by pipelining endless
231
+ * requests.
232
+ */
233
+ facil_defer(.task = http1_on_data_def, .task_type = FIO_PR_LOCK_TASK,
234
+ .uuid = uuid);
235
+ return;
236
+ }
221
237
  }
222
238
  // no routes lead here.
223
239
  fprintf(stderr,
224
240
  "I am lost on a deserted island, no code can reach me here :-)\n");
225
- return; // How did we get here?
241
+ goto finished_reading; // How did we get here?
226
242
  parser_error:
227
243
  if (request->request.headers_count >= HTTP1_MAX_HEADER_COUNT)
228
244
  goto too_big;
@@ -231,34 +247,54 @@ bad_request:
231
247
  {
232
248
  http_response_s *response = http_response_create(&request->request);
233
249
  response->status = 400;
234
- http_response_write_body(response, "Bad Request", 11);
235
- http_response_finish(response);
250
+ if (!pr->settings->public_folder ||
251
+ http_response_sendfile2(response, &request->request,
252
+ pr->settings->public_folder,
253
+ pr->settings->public_folder_length, "400.html",
254
+ 8, pr->settings->log_static)) {
255
+ http_response_write_body(response, "Bad Request", 11);
256
+ http_response_finish(response);
257
+ }
236
258
  sock_close(uuid);
237
259
  request->buffer_pos = 0;
238
- return;
260
+ goto finished_reading;
239
261
  }
240
262
  too_big:
241
263
  /* handle oversized headers */
242
264
  {
243
265
  http_response_s *response = http_response_create(&request->request);
244
266
  response->status = 431;
245
- http_response_write_body(response, "Request Header Fields Too Large", 31);
246
- http_response_finish(response);
267
+ if (!pr->settings->public_folder ||
268
+ http_response_sendfile2(response, &request->request,
269
+ pr->settings->public_folder,
270
+ pr->settings->public_folder_length, "431.html",
271
+ 8, pr->settings->log_static)) {
272
+ http_response_write_body(response, "Request Header Fields Too Large", 31);
273
+ http_response_finish(response);
274
+ }
247
275
  sock_close(uuid);
248
276
  request->buffer_pos = 0;
249
- return;
277
+ goto finished_reading;
250
278
  body_to_big:
251
279
  /* handle oversized body */
252
280
  {
253
281
  http_response_s *response = http_response_create(&request->request);
254
282
  response->status = 413;
255
- http_response_write_body(response, "Payload Too Large", 17);
256
- http_response_finish(response);
283
+ if (!pr->settings->public_folder ||
284
+ http_response_sendfile2(response, &request->request,
285
+ pr->settings->public_folder,
286
+ pr->settings->public_folder_length,
287
+ "413.html", 8, pr->settings->log_static)) {
288
+ http_response_write_body(response, "Payload Too Large", 17);
289
+ http_response_finish(response);
290
+ }
257
291
  sock_close(uuid);
258
292
  request->buffer_pos = 0;
259
- return;
293
+ goto finished_reading;
260
294
  }
261
295
  }
296
+ finished_reading:
297
+ pr->len = 0;
262
298
  }
263
299
 
264
300
  /* *****************************************************************************
@@ -4,10 +4,16 @@ License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
+ #ifndef _GNU_SOURCE
8
+ #define _GNU_SOURCE
9
+ #endif
10
+
7
11
  #include "http.h"
8
12
  #include "http1_request.h"
9
13
 
10
14
  #include <signal.h>
15
+ #include <sys/types.h>
16
+
11
17
  /* *****************************************************************************
12
18
  Unsupported function placeholders
13
19
  ***************************************************************************** */
@@ -7,11 +7,14 @@ Feel free to copy, use and enjoy according to the license provided.
7
7
  #ifndef H_HTTP_REQUEST_H
8
8
  #define H_HTTP_REQUEST_H
9
9
  typedef struct http_request_s http_request_s;
10
+
10
11
  #include "http.h"
11
12
 
12
13
  struct http_request_s {
13
14
  /** the HTTP version, also controlls the `http_request_s` flavor. */
14
15
  enum HTTP_VERSION http_version;
16
+ /** A pointer to the protocol's settings */
17
+ const http_settings_s *settings;
15
18
  /** the HTTP connection identifier. */
16
19
  intptr_t fd;
17
20
  /** this is an opaque pointer, intended for request pooling / chaining */
@@ -4,6 +4,10 @@ License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
+ #ifndef _GNU_SOURCE
8
+ #define _GNU_SOURCE
9
+ #endif
10
+
7
11
  #include "base64.h"
8
12
  #include "http.h"
9
13
  #include "http1_response.h"
@@ -232,6 +236,8 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
232
236
 
233
237
  if (file_path_unsafe && path_unsafe_len == 0)
234
238
  path_unsafe_len = strlen(file_path_unsafe);
239
+ if (!path_unsafe_len)
240
+ return -1;
235
241
 
236
242
  const char *mime = NULL;
237
243
  const char *ext = NULL;
@@ -241,7 +247,7 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
241
247
  // char *fname = malloc(path_safe_len + path_unsafe_len + 1 + 11);
242
248
  if ((path_safe_len + path_unsafe_len) >= (PATH_MAX - 1 - 11))
243
249
  return -1;
244
- char fname[path_safe_len + path_unsafe_len + 1 + 11];
250
+ char fname[path_safe_len + path_unsafe_len + (1 + 11 + 3)];
245
251
  // if (fname == NULL)
246
252
  // return -1;
247
253
  if (file_path_safe)
@@ -249,10 +255,10 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
249
255
  fname[path_safe_len] = 0;
250
256
  // if the last character is a '/', step back.
251
257
  if (file_path_unsafe) {
252
- if (fname[path_safe_len - 1] == '/')
258
+ if (fname[path_safe_len - 1] == '/' && file_path_unsafe[0] == '/')
253
259
  path_safe_len--;
254
- ssize_t tmp = http_decode_url(fname + path_safe_len, file_path_unsafe,
255
- path_unsafe_len);
260
+ ssize_t tmp = http_decode_path(fname + path_safe_len, file_path_unsafe,
261
+ path_unsafe_len);
256
262
  if (tmp < 0)
257
263
  goto no_file;
258
264
  path_safe_len += tmp;
@@ -276,8 +282,27 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
276
282
  // fprintf(stderr, "file name: %s\noriginal request path: %s\n", fname,
277
283
  // req->path);
278
284
  // get file data (prevent folder access and get modification date)
279
- if (stat(fname, &file_data))
280
- goto no_file;
285
+ {
286
+ http_header_s accept =
287
+ http_request_header_find(request, "accept-encoding", 15);
288
+ if (accept.data && strcasestr(accept.data, "gzip")) {
289
+ buffer[0] = 1;
290
+ fname[path_safe_len] = '.';
291
+ fname[path_safe_len + 1] = 'g';
292
+ fname[path_safe_len + 2] = 'z';
293
+ fname[path_safe_len + 3] = 0;
294
+ if (stat(fname, &file_data)) {
295
+ buffer[0] = 0;
296
+ fname[path_safe_len] = 0;
297
+ if (stat(fname, &file_data))
298
+ goto no_file;
299
+ }
300
+ } else {
301
+ buffer[0] = 0;
302
+ if (stat(fname, &file_data))
303
+ goto no_file;
304
+ }
305
+ }
281
306
  // check that we have a file and not something else
282
307
  if (!S_ISREG(file_data.st_mode) && !S_ISLNK(file_data.st_mode))
283
308
  goto no_file;
@@ -288,9 +313,9 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
288
313
  if (log)
289
314
  http_response_log_start(response);
290
315
  }
291
-
292
316
  // we have a file, time to handle response details.
293
317
  int file = open(fname, O_RDONLY);
318
+
294
319
  // free the allocated fname memory
295
320
  // free(fname);
296
321
  // fname = NULL;
@@ -298,6 +323,12 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
298
323
  goto no_fd_available;
299
324
  }
300
325
 
326
+ if (buffer[0]) {
327
+ fname[path_safe_len] = 0;
328
+ http_response_write_header(response, .name = "Content-Encoding",
329
+ .name_len = 16, .value = "gzip", .value_len = 4);
330
+ }
331
+
301
332
  // get the mime type (we have an ext pointer and the string isn't empty)
302
333
  if (ext && ext[1]) {
303
334
  mime = http_response_ext2mime(ext + 1);
@@ -307,8 +338,8 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
307
338
  }
308
339
  }
309
340
  /* add ETag */
310
- uint64_t sip = file_data.st_size;
311
- sip ^= file_data.st_mtime;
341
+ uint64_t sip = (uint64_t)file_data.st_size;
342
+ sip ^= (uint64_t)file_data.st_mtime;
312
343
  sip = siphash24(&sip, sizeof(uint64_t), SIPHASH_DEFAULT_KEY);
313
344
  bscrypt_base64_encode(buffer, (void *)&sip, 8);
314
345
  http_response_write_header(response, .name = "ETag", .name_len = 4,
@@ -441,6 +472,9 @@ void http_response_log_start(http_response_s *response) {
441
472
  prints out the log to stderr.
442
473
  */
443
474
  static void http_response_log_finish(http_response_s *response) {
475
+ #define HTTP_REQUEST_LOG_LIMIT 128
476
+ char buffer[HTTP_REQUEST_LOG_LIMIT];
477
+
444
478
  http_request_s *request = response->request;
445
479
  intptr_t bytes_sent = response->content_length;
446
480
 
@@ -455,8 +489,6 @@ static void http_response_log_finish(http_response_s *response) {
455
489
  // TODO Guess IP address from headers (forwarded) where possible
456
490
  sock_peer_addr_s addrinfo = sock_peer_addr(response->fd);
457
491
 
458
- #define HTTP_REQUEST_LOG_LIMIT 128
459
- char buffer[HTTP_REQUEST_LOG_LIMIT];
460
492
  size_t pos = 0;
461
493
  if (addrinfo.addrlen) {
462
494
  if (inet_ntop(
@@ -0,0 +1,380 @@
1
+ #include "iodine.h"
2
+ #include "iodine_helpers.h"
3
+ #include "iodine_http.h"
4
+ #include "iodine_protocol.h"
5
+ #include "iodine_pubsub.h"
6
+ #include "iodine_websockets.h"
7
+ #include "rb-rack-io.h"
8
+ /*
9
+ Copyright: Boaz segev, 2016-2017
10
+ License: MIT
11
+
12
+ Feel free to copy, use and enjoy according to the license provided.
13
+ */
14
+
15
+ VALUE Iodine;
16
+ VALUE IodineBase;
17
+ VALUE Iodine_Version;
18
+
19
+ ID iodine_fd_var_id;
20
+ ID iodine_timeout_var_id;
21
+ ID iodine_call_proc_id;
22
+ ID iodine_new_func_id;
23
+ ID iodine_on_open_func_id;
24
+ ID iodine_on_message_func_id;
25
+ ID iodine_on_data_func_id;
26
+ ID iodine_on_ready_func_id;
27
+ ID iodine_on_shutdown_func_id;
28
+ ID iodine_on_close_func_id;
29
+ ID iodine_ping_func_id;
30
+ ID iodine_buff_var_id;
31
+ ID iodine_to_s_method_id;
32
+ ID iodine_to_i_func_id;
33
+
34
+ rb_encoding *IodineBinaryEncoding;
35
+ rb_encoding *IodineUTF8Encoding;
36
+ int IodineBinaryEncodingIndex;
37
+ int IodineUTF8EncodingIndex;
38
+
39
+ /* *****************************************************************************
40
+ Internal helpers
41
+ ***************************************************************************** */
42
+
43
+ static void iodine_run_task(void *block_) {
44
+ RubyCaller.call((VALUE)block_, iodine_call_proc_id);
45
+ }
46
+
47
+ static void iodine_perform_deferred(void *block_, void *ignr) {
48
+ RubyCaller.call((VALUE)block_, iodine_call_proc_id);
49
+ Registry.remove((VALUE)block_);
50
+ (void)ignr;
51
+ }
52
+
53
+ /* *****************************************************************************
54
+ Published functions
55
+ ***************************************************************************** */
56
+
57
+ /** Returns the number of total connections managed by Iodine. */
58
+ static VALUE iodine_count(VALUE self) {
59
+ size_t count = facil_count(NULL);
60
+ return ULL2NUM(count);
61
+ (void)self;
62
+ }
63
+
64
+ /**
65
+ Runs the required block after the specified number of milliseconds have passed.
66
+ Time is counted only once Iodine started running (using {Iodine.start}).
67
+
68
+ Tasks scheduled before calling {Iodine.start} will run once for every process.
69
+
70
+ Always returns a copy of the block object.
71
+ */
72
+ static VALUE iodine_run_after(VALUE self, VALUE milliseconds) {
73
+ (void)(self);
74
+ if (TYPE(milliseconds) != T_FIXNUM) {
75
+ rb_raise(rb_eTypeError, "milliseconds must be a number");
76
+ return Qnil;
77
+ }
78
+ size_t milli = FIX2UINT(milliseconds);
79
+ // requires a block to be passed
80
+ rb_need_block();
81
+ VALUE block = rb_block_proc();
82
+ if (block == Qnil)
83
+ return Qfalse;
84
+ Registry.add(block);
85
+ facil_run_every(milli, 1, iodine_run_task, (void *)block,
86
+ (void (*)(void *))Registry.remove);
87
+ return block;
88
+ }
89
+ /**
90
+ Runs the required block after the specified number of milliseconds have passed.
91
+ Time is counted only once Iodine started running (using {Iodine.start}).
92
+
93
+ Accepts:
94
+
95
+ milliseconds:: the number of milliseconds between event repetitions.
96
+
97
+ repetitions:: the number of event repetitions. Defaults to 0 (never ending).
98
+
99
+ block:: (required) a block is required, as otherwise there is nothing to
100
+ perform.
101
+
102
+ The event will repeat itself until the number of repetitions had been delpeted.
103
+
104
+ Always returns a copy of the block object.
105
+ */
106
+ static VALUE iodine_run_every(int argc, VALUE *argv, VALUE self) {
107
+ (void)(self);
108
+ VALUE milliseconds, repetitions, block;
109
+
110
+ rb_scan_args(argc, argv, "11&", &milliseconds, &repetitions, &block);
111
+
112
+ if (TYPE(milliseconds) != T_FIXNUM) {
113
+ rb_raise(rb_eTypeError, "milliseconds must be a number.");
114
+ return Qnil;
115
+ }
116
+ if (repetitions != Qnil && TYPE(repetitions) != T_FIXNUM) {
117
+ rb_raise(rb_eTypeError, "repetitions must be a number or `nil`.");
118
+ return Qnil;
119
+ }
120
+
121
+ size_t milli = FIX2UINT(milliseconds);
122
+ size_t repeat = (repetitions == Qnil) ? 0 : FIX2UINT(repetitions);
123
+ // requires a block to be passed
124
+ rb_need_block();
125
+ Registry.add(block);
126
+ facil_run_every(milli, repeat, iodine_run_task, (void *)block,
127
+ (void (*)(void *))Registry.remove);
128
+ return block;
129
+ }
130
+
131
+ static VALUE iodine_run(VALUE self) {
132
+ rb_need_block();
133
+ VALUE block = rb_block_proc();
134
+ if (block == Qnil)
135
+ return Qfalse;
136
+ Registry.add(block);
137
+ defer(iodine_perform_deferred, (void *)block, NULL);
138
+ return block;
139
+ (void)self;
140
+ }
141
+
142
+ /* *****************************************************************************
143
+ Idling
144
+ ***************************************************************************** */
145
+ #include "fio_list.h"
146
+ #include "spnlock.inc"
147
+
148
+ typedef struct {
149
+ fio_list_s node;
150
+ VALUE block;
151
+ } iodine_idle_block_s;
152
+
153
+ static spn_lock_i iodine_on_idle_lock = SPN_LOCK_INIT;
154
+ static fio_list_s iodine_on_idle_list =
155
+ FIO_LIST_INIT_STATIC(iodine_on_idle_list);
156
+
157
+ /**
158
+ Schedules a single occuring event for the next idle cycle.
159
+
160
+ To schedule a reoccuring event, simply reschedule the event at the end of it's
161
+ run.
162
+
163
+ i.e.
164
+
165
+ IDLE_PROC = Proc.new { puts "idle"; Iodine.on_idle &IDLE_PROC }
166
+ Iodine.on_idle &IDLE_PROC
167
+ */
168
+ VALUE iodine_sched_on_idle(VALUE self) {
169
+ rb_need_block();
170
+ iodine_idle_block_s *b = malloc(sizeof(*b));
171
+ b->block = rb_block_proc();
172
+ Registry.add(b->block);
173
+ spn_lock(&iodine_on_idle_lock);
174
+ fio_list_push(iodine_idle_block_s, node, iodine_on_idle_list, b);
175
+ spn_unlock(&iodine_on_idle_lock);
176
+ return b->block;
177
+ (void)self;
178
+ }
179
+
180
+ static void iodine_on_idle(void) {
181
+ iodine_idle_block_s *b;
182
+ spn_lock(&iodine_on_idle_lock);
183
+ while ((b = fio_list_shift(iodine_idle_block_s, node, iodine_on_idle_list))) {
184
+ defer(iodine_perform_deferred, (void *)b->block, NULL);
185
+ free(b);
186
+ }
187
+ spn_unlock(&iodine_on_idle_lock);
188
+ }
189
+ /* *****************************************************************************
190
+ Running the server
191
+ ***************************************************************************** */
192
+
193
+ #include "spnlock.inc"
194
+ #include <pthread.h>
195
+
196
+ static volatile int sock_io_thread = 0;
197
+ static pthread_t sock_io_pthread;
198
+
199
+ static void *iodine_io_thread(void *arg) {
200
+ (void)arg;
201
+ struct timespec tm;
202
+ // static const struct timespec tm = {.tv_nsec = 524288UL};
203
+ while (sock_io_thread) {
204
+ sock_flush_all();
205
+ tm = (struct timespec){.tv_nsec = 524288UL, .tv_sec = 1};
206
+ nanosleep(&tm, NULL);
207
+ }
208
+ return NULL;
209
+ }
210
+ static void iodine_start_io_thread(void *a1, void *a2) {
211
+ (void)a1;
212
+ (void)a2;
213
+ pthread_create(&sock_io_pthread, NULL, iodine_io_thread, NULL);
214
+ }
215
+ static void iodine_join_io_thread(void) {
216
+ sock_io_thread = 0;
217
+ pthread_join(sock_io_pthread, NULL);
218
+ }
219
+
220
+ static void *srv_start_no_gvl(void *_) {
221
+ (void)(_);
222
+ // collect requested settings
223
+ VALUE rb_th_i = rb_iv_get(Iodine, "@threads");
224
+ VALUE rb_pr_i = rb_iv_get(Iodine, "@processes");
225
+ ssize_t threads = (TYPE(rb_th_i) == T_FIXNUM) ? FIX2LONG(rb_th_i) : 0;
226
+ ssize_t processes = (TYPE(rb_pr_i) == T_FIXNUM) ? FIX2LONG(rb_pr_i) : 0;
227
+ // print a warnning if settings are sub optimal
228
+ #ifdef _SC_NPROCESSORS_ONLN
229
+ size_t cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
230
+ if (processes <= 0)
231
+ processes = 0;
232
+ if (threads <= 0)
233
+ threads = 0;
234
+
235
+ if (processes && threads && cpu_count > 0 &&
236
+ (((size_t)processes << 1) < cpu_count ||
237
+ (size_t)processes > (cpu_count << 1)))
238
+ fprintf(stderr,
239
+ "\n* Performance warnning:\n"
240
+ " - This computer reports %lu available CPUs... "
241
+ "they will not be fully utilized.\n",
242
+ cpu_count);
243
+ #else
244
+ if (processes <= 0)
245
+ processes = 0;
246
+ if (threads <= 0)
247
+ threads = 0;
248
+ #endif
249
+ sock_io_thread = 1;
250
+ defer(iodine_start_io_thread, NULL, NULL);
251
+ fprintf(stderr, "\n");
252
+ facil_run(.threads = threads, .processes = processes,
253
+ .on_idle = iodine_on_idle, .on_finish = iodine_join_io_thread);
254
+ return NULL;
255
+ }
256
+
257
+ static int iodine_review_rack_app(void) {
258
+ /* Check for Iodine::Rack.app and perform the C equivalent:
259
+ * Iodine::HTTP.listen app: @app, port: @port, address: @address, log: @log,
260
+ * max_msg: max_msg, max_body: max_body, public: @public, ping:
261
+ * @ws_timeout, timeout: @timeout
262
+ */
263
+
264
+ VALUE rack = rb_const_get(Iodine, rb_intern("Rack"));
265
+ VALUE app = rb_ivar_get(rack, rb_intern("@app"));
266
+ VALUE www = rb_ivar_get(rack, rb_intern("@public"));
267
+ if ((app == Qnil || app == Qfalse) && (www == Qnil || www == Qfalse))
268
+ return 0;
269
+ VALUE opt = rb_hash_new();
270
+ Registry.add(opt);
271
+
272
+ rb_hash_aset(opt, ID2SYM(rb_intern("app")),
273
+ rb_ivar_get(rack, rb_intern("@app")));
274
+ rb_hash_aset(opt, ID2SYM(rb_intern("port")),
275
+ rb_ivar_get(rack, rb_intern("@port")));
276
+ rb_hash_aset(opt, ID2SYM(rb_intern("app")),
277
+ rb_ivar_get(rack, rb_intern("@app")));
278
+ rb_hash_aset(opt, ID2SYM(rb_intern("address")),
279
+ rb_ivar_get(rack, rb_intern("@address")));
280
+ rb_hash_aset(opt, ID2SYM(rb_intern("log")),
281
+ rb_ivar_get(rack, rb_intern("@log")));
282
+ rb_hash_aset(opt, ID2SYM(rb_intern("max_msg")),
283
+ rb_ivar_get(rack, rb_intern("@max_msg")));
284
+ rb_hash_aset(opt, ID2SYM(rb_intern("max_body")),
285
+ rb_ivar_get(rack, rb_intern("@max_body")));
286
+ rb_hash_aset(opt, ID2SYM(rb_intern("public")),
287
+ rb_ivar_get(rack, rb_intern("@public")));
288
+ rb_hash_aset(opt, ID2SYM(rb_intern("ping")),
289
+ rb_ivar_get(rack, rb_intern("@ping")));
290
+ rb_hash_aset(opt, ID2SYM(rb_intern("timeout")),
291
+ rb_ivar_get(rack, rb_intern("@ws_timeout")));
292
+ if (rb_funcall2(Iodine, rb_intern("listen2http"), 1, &opt) == Qfalse)
293
+ return -1;
294
+ return 0;
295
+ }
296
+
297
+ /**
298
+ Starts the Iodine event loop. This will hang the thread until an interrupt
299
+ (`^C`) signal is received.
300
+
301
+ Returns the Iodine module.
302
+ */
303
+ static VALUE iodine_start(VALUE self) {
304
+ /* for the special Iodine::Rack object and backwards compatibility */
305
+ if (iodine_review_rack_app()) {
306
+ fprintf(stderr, "ERROR: (iodine) cann't start Iodine::Rack.\n");
307
+ return Qnil;
308
+ }
309
+ rb_thread_call_without_gvl2(srv_start_no_gvl, (void *)self, NULL, NULL);
310
+ return self;
311
+ }
312
+
313
+ /* *****************************************************************************
314
+ Debug
315
+ ***************************************************************************** */
316
+
317
+ VALUE iodine_print_registry(VALUE self) {
318
+ Registry.print();
319
+ return Qnil;
320
+ (void)self;
321
+ }
322
+
323
+ /* *****************************************************************************
324
+ Library Initialization
325
+ ***************************************************************************** */
326
+
327
+ ////////////////////////////////////////////////////////////////////////
328
+ // Ruby loads the library and invokes the Init_<lib_name> function...
329
+ //
330
+ // Here we connect all the C code to the Ruby interface, completing the bridge
331
+ // between Lib-Server and Ruby.
332
+ void Init_iodine(void) {
333
+ // initialize globally used IDs, for faster access to the Ruby layer.
334
+ iodine_fd_var_id = rb_intern("scrtfd");
335
+ iodine_call_proc_id = rb_intern("call");
336
+ iodine_new_func_id = rb_intern("new");
337
+ iodine_on_open_func_id = rb_intern("on_open");
338
+ iodine_on_message_func_id = rb_intern("on_message");
339
+ iodine_on_data_func_id = rb_intern("on_data");
340
+ iodine_on_shutdown_func_id = rb_intern("on_shutdown");
341
+ iodine_on_close_func_id = rb_intern("on_close");
342
+ iodine_on_ready_func_id = rb_intern("on_ready");
343
+ iodine_ping_func_id = rb_intern("ping");
344
+ iodine_buff_var_id = rb_intern("scrtbuffer");
345
+ iodine_timeout_var_id = rb_intern("@timeout");
346
+ iodine_to_s_method_id = rb_intern("to_s");
347
+ iodine_to_i_func_id = rb_intern("to_i");
348
+
349
+ IodineBinaryEncodingIndex = rb_enc_find_index("binary");
350
+ IodineUTF8EncodingIndex = rb_enc_find_index("UTF-8");
351
+ IodineBinaryEncoding = rb_enc_find("binary");
352
+ IodineUTF8Encoding = rb_enc_find("UTF-8");
353
+
354
+ // The core Iodine module wraps libserver functionality and little more.
355
+ Iodine = rb_define_module("Iodine");
356
+
357
+ // the Iodine singleton functions
358
+ rb_define_module_function(Iodine, "start", iodine_start, 0);
359
+ rb_define_singleton_method(Iodine, "count", iodine_count, 0);
360
+ rb_define_module_function(Iodine, "run", iodine_run, 0);
361
+ rb_define_module_function(Iodine, "run_after", iodine_run_after, 1);
362
+ rb_define_module_function(Iodine, "run_every", iodine_run_every, -1);
363
+ rb_define_module_function(Iodine, "on_idle", iodine_sched_on_idle, 0);
364
+
365
+ // Every Protocol (and Server?) instance will hold a reference to the server
366
+ // define the Server Ruby class.
367
+ IodineBase = rb_define_module_under(Iodine, "Base");
368
+ rb_define_module_function(IodineBase, "db_print_registry",
369
+ iodine_print_registry, 0);
370
+
371
+ // Initialize the registry under the Iodine core
372
+ Registry.init(Iodine);
373
+
374
+ /* Initialize the rest of the library. */
375
+ Iodine_init_protocol();
376
+ Iodine_init_pubsub();
377
+ Iodine_init_http();
378
+ Iodine_init_websocket();
379
+ Iodine_init_helpers();
380
+ }