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
@@ -0,0 +1,62 @@
1
+ #ifndef H_IODINE_H
2
+ #define H_IODINE_H
3
+ /*
4
+ Copyright: Boaz segev, 2016-2017
5
+ License: MIT
6
+
7
+ Feel free to copy, use and enjoy according to the license provided.
8
+ */
9
+ #include <ruby.h>
10
+ #ifndef _GNU_SOURCE
11
+ #define _GNU_SOURCE
12
+ #endif
13
+ #include <ruby/encoding.h>
14
+ #include <ruby/io.h>
15
+ #include <ruby/thread.h>
16
+ #include <ruby/version.h>
17
+
18
+ #include "rb-call.h"
19
+ #include "rb-registry.h"
20
+
21
+ #include "facil.h"
22
+
23
+ #include <stdio.h>
24
+ #include <stdlib.h>
25
+ #include <string.h>
26
+
27
+ #ifndef UNUSED_FUNC
28
+ #define UNUSED_FUNC __attribute__((unused))
29
+ #endif
30
+
31
+ extern VALUE Iodine;
32
+ extern VALUE IodineBase;
33
+ extern VALUE Iodine_Version;
34
+
35
+ extern ID iodine_fd_var_id;
36
+ extern ID iodine_timeout_var_id;
37
+ extern ID iodine_call_proc_id;
38
+ extern ID iodine_new_func_id;
39
+ extern ID iodine_on_open_func_id;
40
+ extern ID iodine_on_message_func_id;
41
+ extern ID iodine_on_data_func_id;
42
+ extern ID iodine_on_ready_func_id;
43
+ extern ID iodine_on_shutdown_func_id;
44
+ extern ID iodine_on_close_func_id;
45
+ extern ID iodine_ping_func_id;
46
+ extern ID iodine_buff_var_id;
47
+ extern ID iodine_to_s_method_id;
48
+ extern ID iodine_to_i_func_id;
49
+
50
+ extern rb_encoding *IodineBinaryEncoding;
51
+ extern rb_encoding *IodineUTF8Encoding;
52
+ extern int IodineBinaryEncodingIndex;
53
+ extern int IodineUTF8EncodingIndex;
54
+
55
+ UNUSED_FUNC static inline void iodine_set_fd(VALUE handler, intptr_t fd) {
56
+ rb_ivar_set(handler, iodine_fd_var_id, LONG2NUM((long)fd));
57
+ }
58
+ UNUSED_FUNC static inline intptr_t iodine_get_fd(VALUE handler) {
59
+ return ((intptr_t)NUM2LONG(rb_ivar_get(handler, iodine_fd_var_id)));
60
+ }
61
+
62
+ #endif
@@ -0,0 +1,235 @@
1
+ /*
2
+ Copyright: Boaz segev, 2016-2017
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #include "iodine_helpers.h"
8
+
9
+ #include "http.h"
10
+
11
+ /*
12
+ Add all sorts of useless stuff here.
13
+ */
14
+
15
+ /* *****************************************************************************
16
+ URL Decoding
17
+ ***************************************************************************** */
18
+
19
+ /**
20
+ Decodes a URL encoded String in place.
21
+
22
+ Raises an exception on error... but this might result in a partially decoded
23
+ String.
24
+ */
25
+ static VALUE url_decode_inplace(VALUE self, VALUE str) {
26
+ Check_Type(str, T_STRING);
27
+ ssize_t len =
28
+ http_decode_url(RSTRING_PTR(str), RSTRING_PTR(str), RSTRING_LEN(str));
29
+ if (len < 0)
30
+ rb_raise(rb_eRuntimeError, "Malformed URL string - couldn't decode (String "
31
+ "might have been partially altered).");
32
+ rb_str_set_len(str, len);
33
+ return str;
34
+ (void)self;
35
+ }
36
+
37
+ /**
38
+ Decodes a URL encoded String, returning a new String with the decoded data.
39
+ */
40
+ static VALUE url_decode(VALUE self, VALUE str) {
41
+ Check_Type(str, T_STRING);
42
+ VALUE str2 = rb_str_buf_new(RSTRING_LEN(str));
43
+ ssize_t len =
44
+ http_decode_url(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str));
45
+ if (len < 0)
46
+ rb_raise(rb_eRuntimeError, "Malformed URL string - couldn't decode.");
47
+ rb_str_set_len(str2, len);
48
+ return str2;
49
+ (void)self;
50
+ }
51
+
52
+ /**
53
+ Decodes a percent encoded String (normally the "path" of a request), editing the
54
+ String in place.
55
+
56
+ Raises an exception on error... but this might result in a partially decoded
57
+ String.
58
+ */
59
+ static VALUE path_decode_inplace(VALUE self, VALUE str) {
60
+ Check_Type(str, T_STRING);
61
+ ssize_t len =
62
+ http_decode_path(RSTRING_PTR(str), RSTRING_PTR(str), RSTRING_LEN(str));
63
+ if (len < 0)
64
+ rb_raise(rb_eRuntimeError,
65
+ "Malformed URL path string - couldn't decode (String "
66
+ "might have been partially altered).");
67
+ rb_str_set_len(str, len);
68
+ return str;
69
+ (void)self;
70
+ }
71
+
72
+ /**
73
+ Decodes a percent encoded String (normally the "path" of a request), returning a
74
+ new String with the decoded data.
75
+ */
76
+ static VALUE path_decode(VALUE self, VALUE str) {
77
+ Check_Type(str, T_STRING);
78
+ VALUE str2 = rb_str_buf_new(RSTRING_LEN(str));
79
+ ssize_t len =
80
+ http_decode_path(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str));
81
+ if (len < 0)
82
+ rb_raise(rb_eRuntimeError, "Malformed URL path string - couldn't decode.");
83
+ rb_str_set_len(str2, len);
84
+ return str2;
85
+ (void)self;
86
+ }
87
+
88
+ /**
89
+ Decodes a URL encoded String, returning a new String with the decoded data.
90
+
91
+ This variation matches the Rack::Utils.unescape signature by accepting and
92
+ mostly ignoring an optional Encoding argument.
93
+ */
94
+ static VALUE unescape(int argc, VALUE *argv, VALUE self) {
95
+ if (argc < 1 || argc > 2)
96
+ rb_raise(rb_eArgError,
97
+ "wrong number of arguments (given %d, expected 1..2).", argc);
98
+ VALUE str = argv[0];
99
+ Check_Type(str, T_STRING);
100
+ VALUE str2 = rb_str_buf_new(RSTRING_LEN(str));
101
+ ssize_t len =
102
+ http_decode_url(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str));
103
+ if (len < 0)
104
+ rb_raise(rb_eRuntimeError, "Malformed URL path string - couldn't decode.");
105
+ rb_str_set_len(str2, len);
106
+ rb_encoding *enc = IodineUTF8Encoding;
107
+ if (argc == 2 && argv[1] != Qnil && argv[1] != Qfalse) {
108
+ enc = rb_enc_get(argv[1]);
109
+ if (!enc)
110
+ enc = IodineUTF8Encoding;
111
+ }
112
+ rb_enc_associate(str2, enc);
113
+ return str2;
114
+ (void)self;
115
+ }
116
+
117
+ /* *****************************************************************************
118
+ HTTP Dates
119
+ ***************************************************************************** */
120
+
121
+ /**
122
+ Takes an optional Integer for Unix Time and returns a faster (though less
123
+ localized) HTTP Date formatted String.
124
+
125
+
126
+ Iodine::Rack.time2str => "Sun, 11 Jun 2017 06:14:08 GMT"
127
+
128
+ Iodine::Rack.time2str(Time.now.to_i) => "Wed, 15 Nov 1995 06:25:24 GMT"
129
+
130
+ Since Iodine uses time caching within it's reactor, using the default value
131
+ (now) will be faster than providing an explicit time using `Time.now.to_i`.
132
+
133
+ */
134
+ static VALUE date_str(int argc, VALUE *argv, VALUE self) {
135
+ if (argc > 1)
136
+ rb_raise(rb_eArgError,
137
+ "wrong number of arguments (given %d, expected 0..1).", argc);
138
+ time_t last_tick;
139
+ if (argc) {
140
+ if (TYPE(argv[0]) != T_FIXNUM)
141
+ argv[0] = rb_funcallv(argv[0], iodine_to_i_func_id, 0, NULL);
142
+ Check_Type(argv[0], T_FIXNUM);
143
+ last_tick = FIX2ULONG(argv[0]) ? FIX2ULONG(argv[0]) : facil_last_tick();
144
+ } else
145
+ last_tick = facil_last_tick();
146
+ VALUE str = rb_str_buf_new(32);
147
+ struct tm tm;
148
+
149
+ http_gmtime(&last_tick, &tm);
150
+ size_t len = http_date2str(RSTRING_PTR(str), &tm);
151
+ rb_str_set_len(str, len);
152
+ return str;
153
+ (void)self;
154
+ }
155
+
156
+ /**
157
+ Takes `time` and returns a faster (though less localized) HTTP Date formatted
158
+ String.
159
+
160
+
161
+ Iodine::Rack.rfc2822(Time.now) => "Sun, 11 Jun 2017 06:14:08 -0000"
162
+
163
+ Iodine::Rack.rfc2822(0) => "Sun, 11 Jun 2017 06:14:08 -0000"
164
+
165
+ Since Iodine uses time caching within it's reactor, using the default value
166
+ (by passing 0) will be faster than providing an explicit time using `Time.now`.
167
+ */
168
+ static VALUE iodine_rfc2822(VALUE self, VALUE rtm) {
169
+ time_t last_tick;
170
+ rtm = rb_funcallv(rtm, iodine_to_i_func_id, 0, NULL);
171
+ last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : facil_last_tick();
172
+ VALUE str = rb_str_buf_new(34);
173
+ struct tm tm;
174
+
175
+ http_gmtime(&last_tick, &tm);
176
+ size_t len = http_date2rfc2822(RSTRING_PTR(str), &tm);
177
+ rb_str_set_len(str, len);
178
+ return str;
179
+ (void)self;
180
+ }
181
+
182
+ /**
183
+ Takes `time` and returns a faster (though less localized) HTTP Date formatted
184
+ String.
185
+
186
+
187
+ Iodine::Rack.rfc2109(Time.now) => "Sun, 11-Jun-2017 06:14:08 GMT"
188
+
189
+ Iodine::Rack.rfc2109(0) => "Sun, 11-Jun-2017 06:14:08 GMT"
190
+
191
+ Since Iodine uses time caching within it's reactor, using the default value
192
+ (by passing 0) will be faster than providing an explicit time using `Time.now`.
193
+ */
194
+ static VALUE iodine_rfc2109(VALUE self, VALUE rtm) {
195
+ time_t last_tick;
196
+ rtm = rb_funcallv(rtm, iodine_to_i_func_id, 0, NULL);
197
+ last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : facil_last_tick();
198
+ VALUE str = rb_str_buf_new(32);
199
+ struct tm tm;
200
+
201
+ http_gmtime(&last_tick, &tm);
202
+ size_t len = http_date2rfc2109(RSTRING_PTR(str), &tm);
203
+ rb_str_set_len(str, len);
204
+ return str;
205
+ (void)self;
206
+ }
207
+
208
+ /* *****************************************************************************
209
+ Ruby Initialization
210
+ ***************************************************************************** */
211
+
212
+ void Iodine_init_helpers(void) {
213
+ VALUE tmp = rb_define_module_under(Iodine, "Rack");
214
+ tmp = rb_define_module_under(tmp, "Utils");
215
+ rb_define_module_function(tmp, "decode_url!", url_decode_inplace, 1);
216
+ rb_define_module_function(tmp, "decode_url", url_decode, 1);
217
+ rb_define_module_function(tmp, "decode_path!", path_decode_inplace, 1);
218
+ rb_define_module_function(tmp, "decode_path", path_decode, 1);
219
+ rb_define_module_function(tmp, "time2str", date_str, -1);
220
+ rb_define_module_function(tmp, "rfc2109", iodine_rfc2109, 1);
221
+ rb_define_module_function(tmp, "rfc2822", iodine_rfc2822, 1);
222
+
223
+ tmp = rb_define_module_under(IodineBase, "MonkeyPatch");
224
+ tmp = rb_define_module_under(tmp, "RackUtils");
225
+ /* we define it all twice for easier monkey patching */
226
+ rb_define_method(tmp, "unescape", unescape, -1);
227
+ rb_define_method(tmp, "unescape_path", path_decode, 1);
228
+ rb_define_method(tmp, "rfc2109", iodine_rfc2109, 1);
229
+ rb_define_method(tmp, "rfc2822", iodine_rfc2822, 1);
230
+ rb_define_singleton_method(tmp, "unescape", unescape, -1);
231
+ rb_define_singleton_method(tmp, "unescape_path", path_decode, 1);
232
+ rb_define_singleton_method(tmp, "rfc2109", iodine_rfc2109, 1);
233
+ rb_define_singleton_method(tmp, "rfc2822", iodine_rfc2822, 1);
234
+ // rb_define_module_function(IodineUtils, "time2str", date_str, -1);
235
+ }
@@ -0,0 +1,13 @@
1
+ #ifndef H_IODINE_HELPERS_H
2
+ #define H_IODINE_HELPERS_H
3
+ /*
4
+ Copyright: Boaz segev, 2016-2017
5
+ License: MIT
6
+
7
+ Feel free to copy, use and enjoy according to the license provided.
8
+ */
9
+ #include "iodine.h"
10
+
11
+ void Iodine_init_helpers(void);
12
+
13
+ #endif
@@ -5,45 +5,50 @@ License: MIT
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
7
  #include "iodine_http.h"
8
- #include "iodine_websocket.h"
9
- #include "websockets.h"
8
+ #include "iodine_websockets.h"
9
+ #include "rb-rack-io.h"
10
10
 
11
11
  #include <arpa/inet.h>
12
12
  #include <sys/socket.h>
13
13
 
14
- /* the Iodine::Rack HTTP server class*/
15
- VALUE IodineHttp;
14
+ /* *****************************************************************************
15
+ Available Globals
16
+ ***************************************************************************** */
17
+ VALUE IodineHTTP;
18
+
19
+ typedef struct {
20
+ VALUE app;
21
+ VALUE env;
22
+ unsigned long max_msg : 56;
23
+ unsigned ping : 8;
24
+ } iodine_http_settings_s;
25
+
16
26
  /* these three are used also by rb-rack-io.c */
17
- VALUE R_HIJACK;
18
- VALUE R_HIJACK_IO;
19
- VALUE R_HIJACK_CB;
27
+ VALUE IODINE_R_HIJACK;
28
+ VALUE IODINE_R_HIJACK_IO;
29
+ VALUE IODINE_R_HIJACK_CB;
20
30
 
21
31
  VALUE UPGRADE_TCP;
22
32
  VALUE UPGRADE_TCP_Q;
23
33
  VALUE UPGRADE_WEBSOCKET;
24
34
  VALUE UPGRADE_WEBSOCKET_Q;
25
- /* backwards compatibility, temp */
26
- VALUE IODINE_UPGRADE;
27
- VALUE IODINE_WEBSOCKET;
28
35
 
29
36
  static VALUE hijack_func_sym;
30
37
  static ID to_fixnum_func_id;
31
38
  static ID close_method_id;
32
39
  static ID each_method_id;
33
- static _Bool iodine_http_request_logging = 0;
34
- static _Bool iodine_http_static_file_server = 0;
40
+ static ID attach_method_id;
41
+
35
42
  #define rack_declare(rack_name) static VALUE rack_name
36
43
 
37
44
  #define rack_set(rack_name, str) \
38
- (rack_name) = rb_enc_str_new((str), strlen((str)), BinaryEncoding); \
45
+ (rack_name) = rb_enc_str_new((str), strlen((str)), IodineBinaryEncoding); \
39
46
  rb_global_variable(&(rack_name)); \
40
47
  rb_obj_freeze(rack_name);
41
48
 
42
49
  #define rack_autoset(rack_name) rack_set((rack_name), #rack_name)
43
50
 
44
- static uint8_t IODINE_IS_DEVELOPMENT_MODE = 0;
45
-
46
- static VALUE ENV_TEMPLATE;
51
+ // static uint8_t IODINE_IS_DEVELOPMENT_MODE = 0;
47
52
 
48
53
  rack_declare(HTTP_SCHEME);
49
54
  rack_declare(HTTPS_SCHEME);
@@ -63,44 +68,42 @@ rack_declare(R_URL_SCHEME); // rack.url_scheme
63
68
  rack_declare(R_INPUT); // rack.input
64
69
  rack_declare(XSENDFILE); // for X-Sendfile support
65
70
  rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support
66
- // rack_declare(R_HIJACK); // rack.hijack
67
- // rack_declare(R_HIJACK_CB);// rack.hijack_io
71
+ // rack_declare(IODINE_R_HIJACK); // rack.hijack
72
+ // rack_declare(IODINE_R_HIJACK_CB);// rack.hijack_io
68
73
 
69
74
  /* *****************************************************************************
70
- HTTP Protocol initialization
71
- */
72
- /* allow quick access to the Rack app */
73
- static VALUE rack_app_handler = 0;
75
+ Copying data from the C request to the Rack's ENV
76
+ ***************************************************************************** */
74
77
 
75
78
  #define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? ((c) & ~32) : (c))
76
79
 
77
- static inline VALUE copy2env(http_request_s *request) {
78
- VALUE env = rb_hash_dup(ENV_TEMPLATE);
80
+ static inline VALUE copy2env(http_request_s *request, VALUE template) {
81
+ VALUE env = rb_hash_dup(template);
82
+ Registry.add(env);
79
83
  VALUE hname; /* will be used later, both as tmp and to iterate header names */
80
84
  char *pos = NULL;
81
85
  const char *reader = NULL;
82
- Registry.add(env);
83
86
  /* Copy basic data */
84
- rb_hash_aset(
85
- env, REQUEST_METHOD,
86
- rb_enc_str_new(request->method, request->method_len, BinaryEncoding));
87
+ rb_hash_aset(env, REQUEST_METHOD,
88
+ rb_enc_str_new(request->method, request->method_len,
89
+ IodineBinaryEncoding));
87
90
 
88
91
  rb_hash_aset(
89
92
  env, PATH_INFO,
90
- rb_enc_str_new(request->path, request->path_len, BinaryEncoding));
91
- rb_hash_aset(
92
- env, QUERY_STRING,
93
- (request->query
94
- ? rb_enc_str_new(request->query, request->query_len, BinaryEncoding)
95
- : QUERY_ESTRING));
96
- rb_hash_aset(
97
- env, QUERY_STRING,
98
- (request->query
99
- ? rb_enc_str_new(request->query, request->query_len, BinaryEncoding)
100
- : QUERY_ESTRING));
101
-
102
- hname =
103
- rb_enc_str_new(request->version, request->version_len, BinaryEncoding);
93
+ rb_enc_str_new(request->path, request->path_len, IodineBinaryEncoding));
94
+ rb_hash_aset(env, QUERY_STRING,
95
+ (request->query
96
+ ? rb_enc_str_new(request->query, request->query_len,
97
+ IodineBinaryEncoding)
98
+ : QUERY_ESTRING));
99
+ rb_hash_aset(env, QUERY_STRING,
100
+ (request->query
101
+ ? rb_enc_str_new(request->query, request->query_len,
102
+ IodineBinaryEncoding)
103
+ : QUERY_ESTRING));
104
+
105
+ hname = rb_enc_str_new(request->version, request->version_len,
106
+ IodineBinaryEncoding);
104
107
  rb_hash_aset(env, SERVER_PROTOCOL, hname);
105
108
  rb_hash_aset(env, HTTP_VERSION, hname);
106
109
 
@@ -119,7 +122,7 @@ static inline VALUE copy2env(http_request_s *request) {
119
122
  }
120
123
 
121
124
  /* setup input IO + hijack support */
122
- rb_hash_aset(env, R_INPUT, (hname = RackIO.create(request, env)));
125
+ rb_hash_aset(env, R_INPUT, (hname = IodineRackIO.create(request, env)));
123
126
 
124
127
  /* publish upgrade support */
125
128
  if (request->upgrade) {
@@ -128,7 +131,7 @@ static inline VALUE copy2env(http_request_s *request) {
128
131
  }
129
132
 
130
133
  hname = rb_obj_method(hname, hijack_func_sym);
131
- rb_hash_aset(env, R_HIJACK, hname);
134
+ rb_hash_aset(env, IODINE_R_HIJACK, hname);
132
135
 
133
136
  /* handle the HOST header, including the possible host:#### format*/
134
137
  pos = (char *)request->host;
@@ -137,16 +140,16 @@ static inline VALUE copy2env(http_request_s *request) {
137
140
  if (*pos == 0) {
138
141
  rb_hash_aset(
139
142
  env, SERVER_NAME,
140
- rb_enc_str_new(request->host, request->host_len, BinaryEncoding));
143
+ rb_enc_str_new(request->host, request->host_len, IodineBinaryEncoding));
141
144
  rb_hash_aset(env, SERVER_PORT, QUERY_ESTRING);
142
145
  } else {
143
- rb_hash_aset(
144
- env, SERVER_NAME,
145
- rb_enc_str_new(request->host, pos - request->host, BinaryEncoding));
146
+ rb_hash_aset(env, SERVER_NAME,
147
+ rb_enc_str_new(request->host, pos - request->host,
148
+ IodineBinaryEncoding));
146
149
  rb_hash_aset(env, SERVER_PORT,
147
150
  rb_enc_str_new(pos + 1,
148
151
  request->host_len - ((pos + 1) - request->host),
149
- BinaryEncoding));
152
+ IodineBinaryEncoding));
150
153
  }
151
154
 
152
155
  /* default schema to http, it might be updated later */
@@ -159,14 +162,14 @@ static inline VALUE copy2env(http_request_s *request) {
159
162
  strncasecmp("content-length", header.name, 14) == 0) {
160
163
  rb_hash_aset(
161
164
  env, CONTENT_LENGTH,
162
- rb_enc_str_new(header.value, header.value_len, BinaryEncoding));
165
+ rb_enc_str_new(header.value, header.value_len, IodineBinaryEncoding));
163
166
  header = http_request_header_next(request);
164
167
  continue;
165
168
  } else if (header.name_len == 12 &&
166
169
  strncasecmp("content-type", header.name, 12) == 0) {
167
170
  rb_hash_aset(
168
171
  env, CONTENT_TYPE,
169
- rb_enc_str_new(header.value, header.value_len, BinaryEncoding));
172
+ rb_enc_str_new(header.value, header.value_len, IodineBinaryEncoding));
170
173
  header = http_request_header_next(request);
171
174
  continue;
172
175
  } else if (header.name_len == 27 &&
@@ -177,9 +180,9 @@ static inline VALUE copy2env(http_request_s *request) {
177
180
  *((uint32_t *)header.value) == *((uint32_t *)"http")) {
178
181
  rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
179
182
  } else {
180
- rb_hash_aset(
181
- env, R_URL_SCHEME,
182
- rb_enc_str_new(header.value, header.value_len, BinaryEncoding));
183
+ rb_hash_aset(env, R_URL_SCHEME,
184
+ rb_enc_str_new(header.value, header.value_len,
185
+ IodineBinaryEncoding));
183
186
  }
184
187
  } else if (header.name_len == 9 &&
185
188
  strncasecmp("forwarded", header.name, 9) == 0) {
@@ -220,22 +223,26 @@ static inline VALUE copy2env(http_request_s *request) {
220
223
  rb_str_set_len(hname, 5 + header.name_len);
221
224
  rb_hash_aset(
222
225
  env, hname,
223
- rb_enc_str_new(header.value, header.value_len, BinaryEncoding));
226
+ rb_enc_str_new(header.value, header.value_len, IodineBinaryEncoding));
224
227
  header = http_request_header_next(request);
225
228
  }
226
229
  return env;
227
230
  }
228
231
 
232
+ /* *****************************************************************************
233
+ Handling the HTTP response
234
+ ***************************************************************************** */
235
+
229
236
  // itterate through the headers and add them to the response buffer
230
237
  // (we are recycling the request's buffer)
231
238
  static int for_each_header_data(VALUE key, VALUE val, VALUE _res) {
232
239
  // fprintf(stderr, "For_each - headers\n");
233
240
  if (TYPE(key) != T_STRING)
234
- key = RubyCaller.call(key, to_s_method_id);
241
+ key = RubyCaller.call(key, iodine_to_s_method_id);
235
242
  if (TYPE(key) != T_STRING)
236
243
  return ST_CONTINUE;
237
244
  if (TYPE(val) != T_STRING) {
238
- val = RubyCaller.call(val, to_s_method_id);
245
+ val = RubyCaller.call(val, iodine_to_s_method_id);
239
246
  if (TYPE(val) != T_STRING)
240
247
  return ST_STOP;
241
248
  }
@@ -273,13 +280,10 @@ static VALUE for_each_body_string(VALUE str, VALUE _res, int argc, VALUE argv) {
273
280
  if (RSTRING_LEN(str)) {
274
281
  if (http_response_write_body((void *)_res, RSTRING_PTR(str),
275
282
  RSTRING_LEN(str))) {
276
- // fprintf(stderr,
277
- // "Iodine Server Error:"
278
- // "couldn't write response to connection\n");
283
+ // fprintf(stderr, "Iodine Server Error:"
284
+ // "couldn't write response to connection\n");
279
285
  return Qfalse;
280
286
  }
281
- } else {
282
- return Qfalse;
283
287
  }
284
288
  return Qtrue;
285
289
  }
@@ -326,33 +330,39 @@ static inline int ruby2c_response_send(http_response_s *response,
326
330
  return -1;
327
331
  }
328
332
 
333
+ /* *****************************************************************************
334
+ Handling Upgrade cases
335
+ ***************************************************************************** */
336
+
329
337
  static inline int ruby2c_review_upgrade(http_response_s *response,
330
338
  VALUE rbresponse, VALUE env) {
331
339
  VALUE handler;
332
- if ((handler = rb_hash_aref(env, R_HIJACK_CB)) != Qnil) {
340
+ if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_CB)) != Qnil) {
333
341
  // send headers
334
342
  http_response_finish(response);
335
- // remove socket from libsock and libserver
343
+ // remove socket from facil.io
336
344
  facil_attach(response->fd, NULL);
337
345
  // call the callback
338
- VALUE io_ruby = RubyCaller.call(rb_hash_aref(env, R_HIJACK), call_proc_id);
339
- RubyCaller.call2(handler, call_proc_id, 1, &io_ruby);
340
- } else if ((handler = rb_hash_aref(env, R_HIJACK_IO)) != Qnil) {
346
+ VALUE io_ruby = RubyCaller.call(rb_hash_aref(env, IODINE_R_HIJACK),
347
+ iodine_call_proc_id);
348
+ RubyCaller.call2(handler, iodine_call_proc_id, 1, &io_ruby);
349
+ } else if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_IO)) != Qnil) {
341
350
  // send nothing.
342
351
  http_response_destroy(response);
343
- // remove socket from libsock and libserver
352
+ // remove socket from facil.io
344
353
  facil_attach(response->fd, NULL);
345
- } else if ((handler = rb_hash_aref(env, UPGRADE_WEBSOCKET)) != Qnil ||
346
- (handler = rb_hash_aref(env, IODINE_WEBSOCKET)) != Qnil) {
354
+ } else if ((handler = rb_hash_aref(env, UPGRADE_WEBSOCKET)) != Qnil) {
355
+ iodine_http_settings_s *settings = response->request->settings->udata;
347
356
  // use response as existing base for native websocket upgrade
348
- iodine_websocket_upgrade(response->request, response, handler);
349
- } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil ||
350
- (handler = rb_hash_aref(env, IODINE_UPGRADE)) != Qnil) {
357
+ iodine_websocket_upgrade(response->request, response, handler,
358
+ settings->max_msg, settings->ping);
359
+ } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil) {
351
360
  intptr_t fduuid = response->fd;
352
361
  // send headers
353
362
  http_response_finish(response);
354
363
  // upgrade protocol
355
- iodine_upgrade2basic(fduuid, handler);
364
+ VALUE args[2] = {(ULONG2NUM(sock_uuid2fd(fduuid))), handler};
365
+ RubyCaller.call2(Iodine, attach_method_id, 2, args);
356
366
  // prevent response processing.
357
367
  } else {
358
368
  return 0;
@@ -365,16 +375,25 @@ static inline int ruby2c_review_upgrade(http_response_s *response,
365
375
  return 1;
366
376
  }
367
377
 
378
+ /* *****************************************************************************
379
+ Handling HTTP requests
380
+ ***************************************************************************** */
381
+
368
382
  static void *on_rack_request_in_GVL(http_request_s *request) {
369
383
  http_response_s *response = http_response_create(request);
370
- if (iodine_http_request_logging)
384
+ iodine_http_settings_s *settings = request->settings->udata;
385
+ if (request->settings->log_static)
371
386
  http_response_log_start(response);
387
+ if (!settings->app)
388
+ goto err_not_found;
389
+
372
390
  // create /register env variable
373
- VALUE env = copy2env(request);
391
+ VALUE env = copy2env(request, settings->env);
374
392
  // will be used later
375
393
  VALUE tmp;
376
394
  // pass env variable to handler
377
- VALUE rbresponse = RubyCaller.call2(rack_app_handler, call_proc_id, 1, &env);
395
+ VALUE rbresponse =
396
+ RubyCaller.call2(settings->app, iodine_call_proc_id, 1, &env);
378
397
  if (rbresponse == 0 || rbresponse == Qnil)
379
398
  goto internal_error;
380
399
  Registry.add(rbresponse);
@@ -391,20 +410,39 @@ static void *on_rack_request_in_GVL(http_request_s *request) {
391
410
  goto internal_error;
392
411
  // extract the X-Sendfile header (never show original path)
393
412
  // X-Sendfile support only present when iodine sercers static files.
394
- VALUE xfiles = iodine_http_static_file_server
395
- ? rb_hash_delete(response_headers, XSENDFILE)
396
- : Qnil;
397
- // remove XFile's content length headers, as this will be controled by Iodine
398
- if (xfiles != Qnil) {
413
+ VALUE xfiles;
414
+ if (request->settings->public_folder &&
415
+ (xfiles = rb_hash_aref(response_headers, XSENDFILE)) != Qnil) {
416
+ int fr = 0;
417
+ if (OBJ_FROZEN(response_headers)) {
418
+ response_headers = rb_hash_dup(response_headers);
419
+ Registry.add(response_headers);
420
+ fr = 1;
421
+ }
422
+ rb_hash_delete(response_headers, XSENDFILE);
423
+ // remove XFile's content length headers, as this will be controled by
424
+ // Iodine
399
425
  rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER);
426
+ // review each header and write it to the response.
427
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(response));
428
+ if (fr)
429
+ Registry.remove(response_headers);
430
+ // send the file directly and finish
431
+ if (http_response_sendfile2(response, request, RSTRING_PTR(xfiles),
432
+ RSTRING_LEN(xfiles), NULL, 0, 1)) {
433
+ http_response_destroy(response);
434
+ response = http_response_create(request);
435
+ if (request->settings->log_static)
436
+ http_response_log_start(response);
437
+ Registry.remove(rbresponse);
438
+ Registry.remove(env);
439
+ goto err_not_found;
440
+ }
441
+ goto external_done;
400
442
  }
401
443
  // review each header and write it to the response.
402
444
  rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(response));
403
445
  // If the X-Sendfile header was provided, send the file directly and finish
404
- if (xfiles != Qnil &&
405
- http_response_sendfile2(response, request, RSTRING_PTR(xfiles),
406
- RSTRING_LEN(xfiles), NULL, 0, 1) == 0)
407
- goto external_done;
408
446
  // review for belated (post response headers) upgrade.
409
447
  if (ruby2c_review_upgrade(response, rbresponse, env))
410
448
  goto external_done;
@@ -416,20 +454,41 @@ static void *on_rack_request_in_GVL(http_request_s *request) {
416
454
  Registry.remove(env);
417
455
  http_response_finish(response);
418
456
  return NULL;
457
+
419
458
  external_done:
420
459
  Registry.remove(rbresponse);
421
460
  Registry.remove(env);
422
461
  return NULL;
462
+
463
+ err_not_found:
464
+ response->status = 404;
465
+ if (!request->settings->public_folder ||
466
+ http_response_sendfile2(response, request,
467
+ request->settings->public_folder,
468
+ request->settings->public_folder_length,
469
+ "404.html", 8, request->settings->log_static)) {
470
+ http_response_write_body(response, "Error 404, Page Not Found.", 26);
471
+ http_response_finish(response);
472
+ }
473
+ return NULL;
474
+
423
475
  internal_error:
424
- Registry.remove(rbresponse);
476
+ if (rbresponse && rbresponse != Qnil)
477
+ Registry.remove(rbresponse);
425
478
  Registry.remove(env);
426
479
  http_response_destroy(response);
427
480
  response = http_response_create(request);
428
- if (iodine_http_request_logging)
481
+ if (request->settings->log_static)
429
482
  http_response_log_start(response);
430
483
  response->status = 500;
431
- http_response_write_body(response, "Error 500, Internal error.", 26);
432
- http_response_finish(response);
484
+ if (!request->settings->public_folder ||
485
+ http_response_sendfile2(response, request,
486
+ request->settings->public_folder,
487
+ request->settings->public_folder_length,
488
+ "500.html", 8, request->settings->log_static)) {
489
+ http_response_write_body(response, "Error 500, Internal error.", 26);
490
+ http_response_finish(response);
491
+ }
433
492
  return NULL;
434
493
  }
435
494
 
@@ -440,173 +499,296 @@ static void on_rack_request(http_request_s *request) {
440
499
  }
441
500
 
442
501
  /* *****************************************************************************
443
- Initializing basic ENV template
444
- */
502
+ Initializing basic Rack ENV template
503
+ ***************************************************************************** */
445
504
 
446
505
  #define add_str_to_env(env, key, value) \
447
506
  { \
448
- VALUE k = rb_enc_str_new((key), strlen((key)), BinaryEncoding); \
507
+ VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
449
508
  rb_obj_freeze(k); \
450
- VALUE v = rb_enc_str_new((value), strlen((value)), BinaryEncoding); \
509
+ VALUE v = rb_enc_str_new((value), strlen((value)), IodineBinaryEncoding); \
451
510
  rb_obj_freeze(v); \
452
511
  rb_hash_aset(env, k, v); \
453
512
  }
454
513
  #define add_value_to_env(env, key, value) \
455
514
  { \
456
- VALUE k = rb_enc_str_new((key), strlen((key)), BinaryEncoding); \
515
+ VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
457
516
  rb_obj_freeze(k); \
458
- rb_hash_aset(env, k, value); \
517
+ rb_hash_aset((env), k, value); \
459
518
  }
460
519
 
461
- static void init_env_template(void) {
520
+ static void init_env_template(iodine_http_settings_s *set, uint8_t xsendfile) {
462
521
  VALUE tmp;
463
- ENV_TEMPLATE = rb_hash_new();
464
- rb_global_variable(&ENV_TEMPLATE);
522
+ set->env = rb_hash_new();
523
+ Registry.add(set->env);
465
524
 
466
525
  // Start with the stuff Iodine will review.
467
- rb_hash_aset(ENV_TEMPLATE, UPGRADE_WEBSOCKET, Qnil);
468
- rb_hash_aset(ENV_TEMPLATE, UPGRADE_TCP, Qnil);
469
- if (iodine_http_static_file_server) {
470
- add_value_to_env(ENV_TEMPLATE, "sendfile.type", XSENDFILE);
471
- add_value_to_env(ENV_TEMPLATE, "HTTP_X_SENDFILE_TYPE", XSENDFILE);
526
+ rb_hash_aset(set->env, UPGRADE_WEBSOCKET, Qnil);
527
+ rb_hash_aset(set->env, UPGRADE_TCP, Qnil);
528
+ if (xsendfile) {
529
+ add_value_to_env(set->env, "sendfile.type", XSENDFILE);
530
+ add_value_to_env(set->env, "HTTP_X_SENDFILE_TYPE", XSENDFILE);
472
531
  }
473
- rb_hash_aset(ENV_TEMPLATE, UPGRADE_WEBSOCKET_Q, Qnil);
474
- rb_hash_aset(ENV_TEMPLATE, UPGRADE_TCP_Q, Qnil);
475
-
476
- /* backwards compatibility, temp */
477
- rb_hash_aset(ENV_TEMPLATE, IODINE_WEBSOCKET, Qnil);
478
- rb_hash_aset(ENV_TEMPLATE, IODINE_UPGRADE, Qnil);
532
+ rb_hash_aset(set->env, UPGRADE_WEBSOCKET_Q, Qnil);
533
+ rb_hash_aset(set->env, UPGRADE_TCP_Q, Qnil);
479
534
 
480
535
  // add the rack.version
481
536
  tmp = rb_ary_new(); // rb_ary_new is Ruby 2.0 compatible
482
537
  rb_ary_push(tmp, INT2FIX(1));
483
538
  rb_ary_push(tmp, INT2FIX(3));
484
- // rb_ary_push(tmp, rb_enc_str_new("1", 1, BinaryEncoding));
485
- // rb_ary_push(tmp, rb_enc_str_new("3", 1, BinaryEncoding));
486
- add_value_to_env(ENV_TEMPLATE, "rack.version", tmp);
487
- add_value_to_env(ENV_TEMPLATE, "rack.errors", rb_stderr);
488
- add_value_to_env(ENV_TEMPLATE, "rack.multithread", Qtrue);
489
- add_value_to_env(ENV_TEMPLATE, "rack.multiprocess", Qtrue);
490
- add_value_to_env(ENV_TEMPLATE, "rack.run_once", Qfalse);
491
- add_value_to_env(ENV_TEMPLATE, "rack.hijack?", Qtrue);
492
- add_str_to_env(ENV_TEMPLATE, "SCRIPT_NAME", "");
539
+ // rb_ary_push(tmp, rb_enc_str_new("1", 1, IodineBinaryEncoding));
540
+ // rb_ary_push(tmp, rb_enc_str_new("3", 1, IodineBinaryEncoding));
541
+ add_value_to_env(set->env, "rack.version", tmp);
542
+ add_value_to_env(set->env, "rack.errors", rb_stderr);
543
+ add_value_to_env(set->env, "rack.multithread", Qtrue);
544
+ add_value_to_env(set->env, "rack.multiprocess", Qtrue);
545
+ add_value_to_env(set->env, "rack.run_once", Qfalse);
546
+ add_value_to_env(set->env, "rack.hijack?", Qtrue);
547
+ add_str_to_env(set->env, "SCRIPT_NAME", "");
493
548
  }
494
549
  #undef add_str_to_env
495
550
  #undef add_value_to_env
496
551
 
497
552
  /* *****************************************************************************
498
- Rack object API
553
+ Listenninng to HTTP
554
+ *****************************************************************************
499
555
  */
500
556
 
501
- int iodine_http_review(void) {
502
- if ((getenv("RACK_ENV") && !strcasecmp(getenv("RACK_ENV"), "development")) ||
503
- (getenv("RAILS_ENV") && !strcasecmp(getenv("RAILS_ENV"), "development")))
504
- IODINE_IS_DEVELOPMENT_MODE = 1;
505
-
506
- rack_app_handler = rb_iv_get(IodineHttp, "@app");
507
- if (rack_app_handler != Qnil &&
508
- rb_respond_to(rack_app_handler, call_proc_id)) {
509
- VALUE rbport = rb_iv_get(IodineHttp, "@port");
510
- VALUE rbaddress = rb_iv_get(IodineHttp, "@address");
511
- VALUE rbmaxbody = rb_iv_get(IodineHttp, "@max_body_size");
512
- VALUE rbmaxmsg = rb_iv_get(IodineHttp, "@max_msg_size");
513
- VALUE rbwww = rb_iv_get(IodineHttp, "@public");
514
- VALUE rblog = rb_iv_get(IodineHttp, "@log");
515
- VALUE rbtout = rb_iv_get(IodineHttp, "@timeout");
516
- VALUE rbwstout = rb_iv_get(IodineHttp, "@ws_timeout");
517
- const char *port = "3000";
518
- const char *address = NULL;
519
- const char *public_folder = NULL;
520
- size_t max_body_size;
521
- // review port
522
- if (TYPE(rbport) != T_FIXNUM && TYPE(rbport) != T_STRING &&
523
- TYPE(rbport) != Qnil)
524
- rb_raise(rb_eTypeError,
525
- "The port variable must be either a Fixnum or a String.");
526
- if (TYPE(rbport) == T_FIXNUM) {
527
- rbport = rb_funcall2(rbport, rb_intern("to_s"), 0, NULL);
528
- // rb_ivar_set(self, rb_intern("_port"), port);
529
- rb_iv_set(IodineHttp, "@port", rbport);
530
- }
531
- if (TYPE(rbport) == T_STRING)
532
- port = StringValueCStr(rbport);
533
- // review address
534
- if (TYPE(rbaddress) != T_STRING && rbaddress != Qnil)
535
- rb_raise(rb_eTypeError,
536
- "The address variable must be either a String or `nil`.");
537
- if (TYPE(address) == T_STRING)
538
- address = StringValueCStr(rbaddress);
539
- // review public folder
540
- if (TYPE(rbwww) != T_STRING && rbwww != Qnil)
557
+ void *iodine_print_http_msg2_in_gvl(void *d_) {
558
+ // Write message
559
+ struct {
560
+ VALUE www;
561
+ VALUE port;
562
+ } *arg = d_;
563
+ if (arg->www) {
564
+ fprintf(stderr,
565
+ "Iodine HTTP Server on port %s:\n"
566
+ " * Serving static files from %s\n\n",
567
+ StringValueCStr(arg->port), StringValueCStr(arg->www));
568
+ Registry.remove(arg->www);
569
+ }
570
+ Registry.remove(arg->port);
571
+ return NULL;
572
+ }
573
+
574
+ void *iodine_print_http_msg_in_gvl(void *d_) {
575
+ // Write message
576
+ VALUE iodine_version = rb_const_get(Iodine, rb_intern("VERSION"));
577
+ VALUE ruby_version = rb_const_get(Iodine, rb_intern("RUBY_VERSION"));
578
+ struct {
579
+ VALUE www;
580
+ VALUE port;
581
+ } *arg = d_;
582
+ if (arg->www) {
583
+ fprintf(stderr,
584
+ "\nStarting up Iodine HTTP Server on port %s:\n"
585
+ " * Ruby v.%s\n * Iodine v.%s \n"
586
+ " * %lu max concurrent connections / open files\n"
587
+ " * Serving static files from %s\n\n",
588
+ StringValueCStr(arg->port), StringValueCStr(ruby_version),
589
+ StringValueCStr(iodine_version), (size_t)sock_max_capacity(),
590
+ StringValueCStr(arg->www));
591
+ Registry.remove(arg->www);
592
+ } else
593
+ fprintf(stderr,
594
+ "\nStarting up Iodine HTTP Server on port %s:\n"
595
+ " * Ruby v.%s\n * Iodine v.%s \n"
596
+ " * %lu max concurrent connections / open files\n\n",
597
+ StringValueCStr(arg->port), StringValueCStr(ruby_version),
598
+ StringValueCStr(iodine_version), (size_t)sock_max_capacity());
599
+ Registry.remove(arg->port);
600
+
601
+ return NULL;
602
+ }
603
+
604
+ static void iodine_print_http_msg1(void *www, void *port) {
605
+ if (defer_fork_pid())
606
+ return;
607
+ struct {
608
+ void *www;
609
+ void *port;
610
+ } data = {.www = www, .port = port};
611
+ RubyCaller.call_c(iodine_print_http_msg_in_gvl, (void *)&data);
612
+ }
613
+ static void iodine_print_http_msg2(void *www, void *port) {
614
+ if (defer_fork_pid())
615
+ return;
616
+ struct {
617
+ void *www;
618
+ void *port;
619
+ } data = {.www = www, .port = port};
620
+ RubyCaller.call_c(iodine_print_http_msg2_in_gvl, (void *)&data);
621
+ }
622
+
623
+ static void free_iodine_http(intptr_t uuid, void *set_) {
624
+ iodine_http_settings_s *set = set_;
625
+ Registry.remove(set->app);
626
+ Registry.remove(set->env);
627
+ free(set);
628
+ (void)uuid;
629
+ }
630
+ /**
631
+ Listens to incoming HTTP connections and handles incoming requests using the
632
+ Rack specification.
633
+
634
+ This is delegated to a lower level C HTTP and Websocket implementation, no
635
+ Ruby object will be crated except the `env` object required by the Rack
636
+ specifications.
637
+
638
+ Accepts a single Hash argument with the following properties:
639
+
640
+ app:: the Rack application that handles incoming requests. Default: `nil`.
641
+ port:: the port to listen to. Default: 3000.
642
+ address:: the address to bind to. Default: binds to all possible addresses.
643
+ log:: enable response logging (Hijacked sockets aren't logged). Default: off.
644
+ public:: The root public folder for static file service. Default: none.
645
+ timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 5 seconds.
646
+ max_body:: The maximum body size for incoming HTTP messages. Default: ~50Mib.
647
+ max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
648
+ ping:: The Websocket `ping` interval. Default: 40 sec.
649
+
650
+ Either the `app` or the `public` properties are required. If niether exists,
651
+ the function will fail. If both exist, Iodine will serve static files as well
652
+ as dynamic requests.
653
+
654
+ When using the static file server, it's possible to serve `gzip` versions of
655
+ the static files by saving a compressed version with the `gz` extension (i.e.
656
+ `styles.css.gz`).
657
+
658
+ `gzip` will only be served to clients tat support the `gzip` transfer
659
+ encoding.
660
+
661
+ Once HTTP/2 is supported (planned, but probably very far away), HTTP/2
662
+ timeouts will be dynamically managed by Iodine. The `timeout` option is only
663
+ relevant to HTTP/1.x connections.
664
+ */
665
+ VALUE iodine_http_listen(VALUE self, VALUE opt) {
666
+ static int called_once = 0;
667
+ uint8_t log_http = 0;
668
+ size_t ping = 0;
669
+ size_t max_body = 0;
670
+ size_t max_msg = 0;
671
+ Check_Type(opt, T_HASH);
672
+ VALUE app = rb_hash_aref(opt, ID2SYM(rb_intern("app")));
673
+ VALUE www = rb_hash_aref(opt, ID2SYM(rb_intern("public")));
674
+ VALUE port = rb_hash_aref(opt, ID2SYM(rb_intern("port")));
675
+ VALUE address = rb_hash_aref(opt, ID2SYM(rb_intern("address")));
676
+ VALUE tout = rb_hash_aref(opt, ID2SYM(rb_intern("timeout")));
677
+
678
+ VALUE tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_msg")));
679
+ if (tmp != Qnil && tmp != Qfalse) {
680
+ Check_Type(tmp, T_FIXNUM);
681
+ max_msg = FIX2ULONG(tmp);
682
+ }
683
+
684
+ tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_body")));
685
+ if (tmp != Qnil && tmp != Qfalse) {
686
+ Check_Type(tmp, T_FIXNUM);
687
+ max_body = FIX2ULONG(tmp);
688
+ }
689
+
690
+ tmp = rb_hash_aref(opt, ID2SYM(rb_intern("ping")));
691
+ if (tmp != Qnil && tmp != Qfalse) {
692
+ Check_Type(tmp, T_FIXNUM);
693
+ ping = FIX2ULONG(tmp);
694
+ }
695
+ if (ping > 255) {
696
+ fprintf(stderr, "Iodine Warning: Websocket timeout value "
697
+ "is over 255 and is silently ignored.\n");
698
+ ping = 0;
699
+ }
700
+
701
+ tmp = rb_hash_aref(opt, ID2SYM(rb_intern("log")));
702
+ if (tmp != Qnil && tmp != Qfalse)
703
+ log_http = 1;
704
+
705
+ if ((app == Qnil || app == Qfalse) && (www == Qnil || www == Qfalse)) {
706
+ fprintf(stderr, "Iodine Warning: HTTP without application or public folder "
707
+ "(is silently ignored).\n");
708
+ return Qfalse;
709
+ }
710
+
711
+ if ((www != Qnil && www != Qfalse)) {
712
+ Check_Type(www, T_STRING);
713
+ Registry.add(www);
714
+ } else
715
+ www = 0;
716
+
717
+ if ((address != Qnil && address != Qfalse))
718
+ Check_Type(address, T_STRING);
719
+ else
720
+ address = 0;
721
+
722
+ if ((tout != Qnil && tout != Qfalse)) {
723
+ Check_Type(tout, T_FIXNUM);
724
+ tout = FIX2ULONG(tout);
725
+ } else
726
+ tout = 0;
727
+ if (tout > 255) {
728
+ fprintf(stderr, "Iodine Warning: HTTP timeout value "
729
+ "is over 255 and is silently ignored.\n");
730
+ tout = 0;
731
+ }
732
+
733
+ if (port != Qnil && port != Qfalse) {
734
+ if (!RB_TYPE_P(port, T_STRING) && !RB_TYPE_P(port, T_FIXNUM))
541
735
  rb_raise(rb_eTypeError,
542
- "The public folder variable `public` must be either a String or "
543
- "`nil`.");
544
- if (TYPE(rbwww) == T_STRING) {
545
- public_folder = StringValueCStr(rbwww);
546
- iodine_http_static_file_server = 1;
547
- }
548
- // review timeout
549
- uint8_t timeout = (TYPE(rbtout) == T_FIXNUM) ? FIX2ULONG(rbtout) : 0;
550
- if (FIX2ULONG(rbtout) > 255) {
551
- fprintf(stderr,
552
- "Iodine Warning: Iodine::Rack timeout value is over 255 and is "
553
- "silently ignored.\n");
554
- timeout = 0;
555
- }
556
- // review websocket timeout
557
- iodine_websocket_timeout =
558
- (TYPE(rbwstout) == T_FIXNUM) ? FIX2ULONG(rbwstout) : 0;
559
- if (FIX2ULONG(rbwstout) > 255) {
560
- fprintf(stderr, "Iodine Warning: Iodine::Rack Websocket timeout value "
561
- "is over 255 and is silently ignored.\n");
562
- iodine_websocket_timeout = 0;
563
- }
564
- // review max body size
565
- max_body_size = (TYPE(rbmaxbody) == T_FIXNUM) ? FIX2ULONG(rbmaxbody) : 0;
566
- // review max websocket message size
567
- iodine_websocket_max_msg_size =
568
- (TYPE(rbmaxmsg) == T_FIXNUM) ? FIX2ULONG(rbmaxmsg) : 0;
569
- // review logging
570
- iodine_http_request_logging = (rblog != Qnil && rblog != Qfalse);
571
-
572
- // initialize the Rack env template
573
- init_env_template();
574
-
575
- // Write message
576
- VALUE iodine_version = rb_const_get(Iodine, rb_intern("VERSION"));
577
- VALUE ruby_version = rb_const_get(Iodine, rb_intern("RUBY_VERSION"));
578
- if (public_folder)
579
- fprintf(stderr,
580
- "Starting up Iodine HTTP Server:\n"
581
- " * Ruby v.%s\n * Iodine v.%s \n"
582
- " * %lu max concurrent connections / open files\n"
583
- " * Serving static files from:\n"
584
- " %s\n\n",
585
- StringValueCStr(ruby_version), StringValueCStr(iodine_version),
586
- (size_t)sock_max_capacity(), public_folder);
587
- else
588
- fprintf(stderr,
589
- "Starting up Iodine HTTP Server:\n"
590
- " * Ruby v.%s\n * Iodine v.%s \n"
591
- " * %lu max concurrent connections / open files\n"
592
- "\n",
593
- StringValueCStr(ruby_version), StringValueCStr(iodine_version),
594
- (size_t)sock_max_capacity());
595
-
596
- // listen
597
- return http_listen(port, address, .on_request = on_rack_request,
598
- .log_static = iodine_http_request_logging,
599
- .max_body_size = max_body_size,
600
- .public_folder = public_folder, .timeout = timeout);
736
+ "The `port` property MUST be either a String or a Number");
737
+ if (RB_TYPE_P(port, T_FIXNUM))
738
+ port = rb_funcall2(port, iodine_to_s_method_id, 0, NULL);
739
+ } else
740
+ port = rb_str_new("3000", 4);
741
+ Registry.add(port);
742
+
743
+ if ((app != Qnil && app != Qfalse))
744
+ Registry.add(app);
745
+ else
746
+ app = 0;
747
+
748
+ iodine_http_settings_s *set = malloc(sizeof(*set));
749
+ *set = (iodine_http_settings_s){.app = app, .ping = ping, .max_msg = max_msg};
750
+
751
+ init_env_template(set, (www ? 1 : 0));
752
+
753
+ if (http_listen(StringValueCStr(port),
754
+ (address ? StringValueCStr(address) : NULL),
755
+ .on_request = on_rack_request, .udata = set,
756
+ .timeout = (tout ? FIX2INT(tout) : tout),
757
+ .on_finish = free_iodine_http, .log_static = log_http,
758
+ .max_body_size = max_body,
759
+ .public_folder = (www ? StringValueCStr(www) : NULL))) {
760
+ fprintf(stderr,
761
+ "ERROR: Failed to initialize a listening HTTP socket for port %s\n",
762
+ port ? StringValueCStr(port) : "3000");
763
+ return Qfalse;
764
+ }
765
+
766
+ if ((app == Qnil || app == Qfalse)) {
767
+ fprintf(stderr,
768
+ "* Iodine: (no app) the HTTP service on port %s will only serve "
769
+ "static files.\n",
770
+ (port ? StringValueCStr(port) : "3000"));
601
771
  }
602
- return 0;
772
+ if (called_once)
773
+ defer(iodine_print_http_msg2, (www ? (void *)www : NULL), (void *)port);
774
+ else {
775
+ called_once = 1;
776
+ defer(iodine_print_http_msg1, (www ? (void *)www : NULL), (void *)port);
777
+ }
778
+
779
+ return Qtrue;
780
+ (void)self;
603
781
  }
604
782
 
605
783
  /* *****************************************************************************
606
- Initializing the library
784
+ Initialization
785
+ *****************************************************************************
607
786
  */
608
787
 
609
- void Init_iodine_http(void) {
788
+ void Iodine_init_http(void) {
789
+
790
+ rb_define_module_function(Iodine, "listen2http", iodine_http_listen, 1);
791
+
610
792
  rack_autoset(REQUEST_METHOD);
611
793
  rack_autoset(PATH_INFO);
612
794
  rack_autoset(QUERY_STRING);
@@ -625,9 +807,9 @@ void Init_iodine_http(void) {
625
807
  rack_set(XSENDFILE, "X-Sendfile");
626
808
  rack_set(CONTENT_LENGTH_HEADER, "Content-Length");
627
809
 
628
- rack_set(R_HIJACK_IO, "rack.hijack_io");
629
- rack_set(R_HIJACK, "rack.hijack");
630
- rack_set(R_HIJACK_CB, "iodine.hijack_cb");
810
+ rack_set(IODINE_R_HIJACK_IO, "rack.hijack_io");
811
+ rack_set(IODINE_R_HIJACK, "rack.hijack");
812
+ rack_set(IODINE_R_HIJACK_CB, "iodine.hijack_cb");
631
813
 
632
814
  rack_set(UPGRADE_TCP, "upgrade.tcp");
633
815
  rack_set(UPGRADE_WEBSOCKET, "upgrade.websocket");
@@ -635,27 +817,13 @@ void Init_iodine_http(void) {
635
817
  rack_set(UPGRADE_TCP_Q, "upgrade.tcp?");
636
818
  rack_set(UPGRADE_WEBSOCKET_Q, "upgrade.websocket?");
637
819
 
638
- /* backwards compatability, temp */
639
- rack_set(IODINE_UPGRADE, "iodine.upgrade");
640
- rack_set(IODINE_WEBSOCKET, "iodine.websocket");
641
-
642
820
  rack_set(QUERY_ESTRING, "");
643
821
  rack_set(QUERY_ESTRING, "");
644
822
 
645
823
  hijack_func_sym = ID2SYM(rb_intern("_hijack"));
646
- to_fixnum_func_id = rb_intern("to_i");
647
824
  close_method_id = rb_intern("close");
648
825
  each_method_id = rb_intern("each");
826
+ attach_method_id = rb_intern("attach_fd");
649
827
 
650
- IodineHttp = rb_define_module_under(Iodine, "Rack");
651
- RackIO.init();
652
- Init_iodine_websocket();
828
+ IodineRackIO.init();
653
829
  }
654
-
655
- // REQUEST_METHOD
656
- // PATH_INFO
657
- // QUERY_STRING
658
- // SERVER_NAME
659
- // SERVER_PORT
660
- // CONTENT_LENGTH
661
- // rack.url_scheme rack.input rack.hijack rack.hijack_io HTTP_ Variables