iodine 0.7.16 → 0.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +5 -4
- data/.yardopts +8 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +1 -1
- data/LIMITS.md +6 -0
- data/README.md +93 -13
- data/{SPEC-Websocket-Draft.md → SPEC-WebSocket-Draft.md} +0 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +3 -3
- data/ext/iodine/extconf.rb +21 -0
- data/ext/iodine/fio.c +659 -69
- data/ext/iodine/fio.h +350 -95
- data/ext/iodine/fio_cli.c +4 -3
- data/ext/iodine/fio_json_parser.h +1 -1
- data/ext/iodine/fio_siphash.c +13 -11
- data/ext/iodine/fio_siphash.h +6 -3
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +634 -0
- data/ext/iodine/fio_tls_openssl.c +1011 -0
- data/ext/iodine/fio_tmpfile.h +1 -1
- data/ext/iodine/fiobj.h +1 -1
- data/ext/iodine/fiobj_ary.c +1 -1
- data/ext/iodine/fiobj_ary.h +1 -1
- data/ext/iodine/fiobj_data.c +1 -1
- data/ext/iodine/fiobj_data.h +1 -1
- data/ext/iodine/fiobj_hash.c +1 -1
- data/ext/iodine/fiobj_hash.h +1 -1
- data/ext/iodine/fiobj_json.c +18 -16
- data/ext/iodine/fiobj_json.h +1 -1
- data/ext/iodine/fiobj_mustache.c +4 -0
- data/ext/iodine/fiobj_mustache.h +4 -0
- data/ext/iodine/fiobj_numbers.c +1 -1
- data/ext/iodine/fiobj_numbers.h +1 -1
- data/ext/iodine/fiobj_str.c +3 -3
- data/ext/iodine/fiobj_str.h +1 -1
- data/ext/iodine/fiobject.c +1 -1
- data/ext/iodine/fiobject.h +8 -2
- data/ext/iodine/http.c +128 -337
- data/ext/iodine/http.h +11 -18
- data/ext/iodine/http1.c +6 -6
- data/ext/iodine/http1.h +1 -1
- data/ext/iodine/http1_parser.c +1 -1
- data/ext/iodine/http1_parser.h +1 -1
- data/ext/iodine/http_internal.c +10 -8
- data/ext/iodine/http_internal.h +13 -3
- data/ext/iodine/http_mime_parser.h +1 -1
- data/ext/iodine/iodine.c +806 -22
- data/ext/iodine/iodine.h +33 -0
- data/ext/iodine/iodine_connection.c +23 -18
- data/ext/iodine/iodine_http.c +239 -225
- data/ext/iodine/iodine_http.h +4 -1
- data/ext/iodine/iodine_mustache.c +59 -54
- data/ext/iodine/iodine_pubsub.c +1 -1
- data/ext/iodine/iodine_tcp.c +34 -100
- data/ext/iodine/iodine_tcp.h +4 -0
- data/ext/iodine/iodine_tls.c +267 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1 -1
- data/ext/iodine/redis_engine.c +14 -6
- data/ext/iodine/redis_engine.h +1 -1
- data/ext/iodine/resp_parser.h +1 -1
- data/ext/iodine/websocket_parser.h +1 -1
- data/ext/iodine/websockets.c +1 -1
- data/ext/iodine/websockets.h +1 -1
- data/iodine.gemspec +2 -1
- data/lib/iodine.rb +19 -5
- data/lib/iodine/connection.rb +13 -0
- data/lib/iodine/mustache.rb +7 -24
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -1
- metadata +15 -5
data/ext/iodine/iodine.h
CHANGED
@@ -3,6 +3,36 @@
|
|
3
3
|
|
4
4
|
#include "ruby.h"
|
5
5
|
|
6
|
+
#include "fio.h"
|
7
|
+
#include "fio_tls.h"
|
8
|
+
#include "fiobj.h"
|
9
|
+
/* used for iodine_connect and iodine_listen routing */
|
10
|
+
typedef struct {
|
11
|
+
fio_str_info_s address;
|
12
|
+
fio_str_info_s port;
|
13
|
+
fio_str_info_s method;
|
14
|
+
fio_str_info_s path;
|
15
|
+
fio_str_info_s body;
|
16
|
+
fio_str_info_s public;
|
17
|
+
fio_str_info_s url;
|
18
|
+
fio_tls_s *tls;
|
19
|
+
VALUE handler;
|
20
|
+
FIOBJ headers;
|
21
|
+
FIOBJ cookies;
|
22
|
+
size_t max_headers;
|
23
|
+
size_t max_body;
|
24
|
+
intptr_t max_clients;
|
25
|
+
size_t max_msg;
|
26
|
+
uint8_t timeout;
|
27
|
+
uint8_t ping;
|
28
|
+
uint8_t log;
|
29
|
+
enum {
|
30
|
+
IODINE_SERVICE_RAW,
|
31
|
+
IODINE_SERVICE_HTTP,
|
32
|
+
IODINE_SERVICE_WS,
|
33
|
+
} service;
|
34
|
+
} iodine_connection_args_s;
|
35
|
+
|
6
36
|
#include "iodine_caller.h"
|
7
37
|
#include "iodine_connection.h"
|
8
38
|
#include "iodine_defer.h"
|
@@ -14,12 +44,15 @@
|
|
14
44
|
#include "iodine_rack_io.h"
|
15
45
|
#include "iodine_store.h"
|
16
46
|
#include "iodine_tcp.h"
|
47
|
+
#include "iodine_tls.h"
|
17
48
|
|
18
49
|
/* *****************************************************************************
|
19
50
|
Constants
|
20
51
|
***************************************************************************** */
|
21
52
|
extern VALUE IodineModule;
|
22
53
|
extern VALUE IodineBaseModule;
|
54
|
+
extern VALUE iodine_default_args;
|
55
|
+
extern ID iodine_call_id;
|
23
56
|
|
24
57
|
#define IODINE_RSTRINFO(rstr) \
|
25
58
|
((fio_str_info_s){.len = RSTRING_LEN(rstr), .data = RSTRING_PTR(rstr)})
|
@@ -51,14 +51,14 @@ Pub/Sub storage
|
|
51
51
|
|
52
52
|
static inline VALUE iodine_sub_unsubscribe(fio_subhash_s *store,
|
53
53
|
fio_str_info_s channel) {
|
54
|
-
if (fio_subhash_remove(store,
|
55
|
-
NULL))
|
54
|
+
if (fio_subhash_remove(store, fiobj_hash_string(channel.data, channel.len),
|
55
|
+
channel, NULL))
|
56
56
|
return Qfalse;
|
57
57
|
return Qtrue;
|
58
58
|
}
|
59
59
|
static inline void iodine_sub_add(fio_subhash_s *store, subscription_s *sub) {
|
60
60
|
fio_str_info_s ch = fio_subscription_channel(sub);
|
61
|
-
fio_subhash_insert(store,
|
61
|
+
fio_subhash_insert(store, fiobj_hash_string(ch.data, ch.len), ch, sub, NULL);
|
62
62
|
}
|
63
63
|
static inline void iodine_sub_clear_all(fio_subhash_s *store) {
|
64
64
|
fio_subhash_free(store);
|
@@ -357,7 +357,7 @@ static VALUE iodine_connection_handler_set(VALUE self, VALUE handler) {
|
|
357
357
|
}
|
358
358
|
if (data->info.handler != handler) {
|
359
359
|
uint8_t answers_on_open = (rb_respond_to(handler, on_open_id) != 0);
|
360
|
-
if(data->answers_on_close)
|
360
|
+
if (data->answers_on_close)
|
361
361
|
IodineCaller.call2(data->info.handler, on_close_id, 1, &self);
|
362
362
|
fio_lock(&data->lock);
|
363
363
|
data->info.handler = handler;
|
@@ -395,8 +395,13 @@ static void iodine_on_pubsub(fio_msg_s *msg) {
|
|
395
395
|
iodine_connection_data_s *data = msg->udata1;
|
396
396
|
VALUE block = (VALUE)msg->udata2;
|
397
397
|
switch (block) {
|
398
|
+
case 0: /* fallthrough */
|
398
399
|
case Qnil: /* fallthrough */
|
399
400
|
case Qtrue: { /* Qtrue == binary WebSocket */
|
401
|
+
if (!data) {
|
402
|
+
FIO_LOG_ERROR("Pub/Sub direct called with no connection data!");
|
403
|
+
return;
|
404
|
+
}
|
400
405
|
if (data->info.handler == Qnil || data->info.uuid == -1 ||
|
401
406
|
fio_is_closed(data->info.uuid))
|
402
407
|
return;
|
@@ -423,7 +428,7 @@ static void iodine_on_pubsub(fio_msg_s *msg) {
|
|
423
428
|
}
|
424
429
|
}
|
425
430
|
default:
|
426
|
-
if (data->info.uuid != -1) {
|
431
|
+
if (data && data->info.uuid != -1) {
|
427
432
|
fio_protocol_s *pr =
|
428
433
|
fio_protocol_try_lock(data->info.uuid, FIO_PR_LOCK_TASK);
|
429
434
|
if (!pr) {
|
@@ -560,13 +565,10 @@ The second, optional, argument must be a Hash (if given).
|
|
560
565
|
|
561
566
|
The options Hash supports the following possible keys (other keys are ignored, all keys are Symbols):
|
562
567
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
:as :: (only for WebSocket connections) accepts the optional value `:binary`. default is `:text`. Note that binary transmissions are illegal for some connections (such as SSE) and an attempted binary subscription will fail for these connections.
|
568
|
-
|
569
|
-
:handler :: Any object that answers `#call(source, msg)` where source is the stream / channel name.
|
568
|
+
- `:match` - The channel / subject name matching type to be used. Valid value is: `:redis`. Future versions hope to support `:nats` and `:rabbit` patern matching as well.
|
569
|
+
- `:to` - The channel / subject to subscribe to.
|
570
|
+
- `:as` - (only for WebSocket connections) accepts the optional value `:binary`. default is `:text`. Note that binary transmissions are illegal for some connections (such as SSE) and an attempted binary subscription will fail for these connections.
|
571
|
+
- `:handler` - Any object that answers `.call(source, msg)` where source is the stream / channel name.
|
570
572
|
|
571
573
|
Note: if an existing subscription with the same name exists, it will be replaced by this new subscription.
|
572
574
|
|
@@ -676,11 +678,9 @@ The method accepts an optional `engine` argument:
|
|
676
678
|
|
677
679
|
Alternatively, accepts the following named arguments:
|
678
680
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
:engine :: If provided, the engine to use for pub/sub. Otherwise the default engine is used.
|
681
|
+
- `:to` - The channel to publish to (required).
|
682
|
+
- `:message` - The message to be published (required).
|
683
|
+
- `:engine` - If provided, the engine to use for pub/sub. Otherwise the default engine is used.
|
684
684
|
|
685
685
|
*/
|
686
686
|
static VALUE iodine_pubsub_publish(int argc, VALUE *argv, VALUE self) {
|
@@ -771,7 +771,7 @@ VALUE iodine_connection_new(iodine_connection_s args) {
|
|
771
771
|
void iodine_connection_fire_event(VALUE connection,
|
772
772
|
iodine_connection_event_type_e ev,
|
773
773
|
VALUE msg) {
|
774
|
-
if (connection == Qnil) {
|
774
|
+
if (!connection || connection == Qnil) {
|
775
775
|
FIO_LOG_ERROR(
|
776
776
|
"(iodine) nil connection handle used by an internal API call");
|
777
777
|
return;
|
@@ -783,6 +783,11 @@ void iodine_connection_fire_event(VALUE connection,
|
|
783
783
|
(void *)connection);
|
784
784
|
return;
|
785
785
|
}
|
786
|
+
if (!data->info.handler || data->info.handler == Qnil) {
|
787
|
+
FIO_LOG_DEBUG("(iodine) invalid connection handler, can't fire event %d",
|
788
|
+
(int)ev);
|
789
|
+
return;
|
790
|
+
}
|
786
791
|
VALUE args[2] = {connection, msg};
|
787
792
|
switch (ev) {
|
788
793
|
case IODINE_CONNECTION_ON_OPEN:
|
data/ext/iodine/iodine_http.c
CHANGED
@@ -38,6 +38,13 @@ static VALUE RACK_UPGRADE_SSE;
|
|
38
38
|
static VALUE RACK_UPGRADE_WEBSOCKET;
|
39
39
|
static VALUE UPGRADE_TCP;
|
40
40
|
|
41
|
+
static VALUE HTTP_ACCEPT;
|
42
|
+
static VALUE HTTP_USER_AGENT;
|
43
|
+
static VALUE HTTP_ACCEPT_ENCODING;
|
44
|
+
static VALUE HTTP_ACCEPT_LANGUAGE;
|
45
|
+
static VALUE HTTP_CONNECTION;
|
46
|
+
static VALUE HTTP_HOST;
|
47
|
+
|
41
48
|
static VALUE hijack_func_sym;
|
42
49
|
static ID close_method_id;
|
43
50
|
static ID each_method_id;
|
@@ -54,9 +61,6 @@ static rb_encoding *IodineBinaryEncoding;
|
|
54
61
|
|
55
62
|
static uint8_t support_xsendfile = 0;
|
56
63
|
|
57
|
-
/** Used by {listen2http} to set missing arguments. */
|
58
|
-
VALUE iodine_default_args;
|
59
|
-
|
60
64
|
#define rack_declare(rack_name) static VALUE rack_name
|
61
65
|
|
62
66
|
#define rack_set(rack_name, str) \
|
@@ -251,7 +255,20 @@ static int iodine_copy2env_task(FIOBJ o, void *env_) {
|
|
251
255
|
FIOBJ name = fiobj_hash_key_in_loop();
|
252
256
|
fio_str_info_s tmp = fiobj_obj2cstr(name);
|
253
257
|
VALUE hname = (VALUE)0;
|
254
|
-
|
258
|
+
/* test for common header names, using pre-allocated memory */
|
259
|
+
if (tmp.len == 6 && !memcmp("accept", tmp.data, 6)) {
|
260
|
+
hname = HTTP_ACCEPT;
|
261
|
+
} else if (tmp.len == 10 && !memcmp("user-agent", tmp.data, 10)) {
|
262
|
+
hname = HTTP_USER_AGENT;
|
263
|
+
} else if (tmp.len == 15 && !memcmp("accept-encoding", tmp.data, 15)) {
|
264
|
+
hname = HTTP_ACCEPT_ENCODING;
|
265
|
+
} else if (tmp.len == 15 && !memcmp("accept-language", tmp.data, 15)) {
|
266
|
+
hname = HTTP_ACCEPT_LANGUAGE;
|
267
|
+
} else if (tmp.len == 10 && !memcmp("connection", tmp.data, 10)) {
|
268
|
+
hname = HTTP_CONNECTION;
|
269
|
+
} else if (tmp.len == 4 && !memcmp("host", tmp.data, 4)) {
|
270
|
+
hname = HTTP_HOST;
|
271
|
+
} else if (tmp.len > 59) {
|
255
272
|
char *buf = fio_malloc(tmp.len + 5);
|
256
273
|
memcpy(buf, "HTTP_", 5);
|
257
274
|
for (size_t i = 0; i < tmp.len; ++i) {
|
@@ -339,7 +356,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
|
|
339
356
|
/* handle the HOST header, including the possible host:#### format*/
|
340
357
|
static uint64_t host_hash = 0;
|
341
358
|
if (!host_hash)
|
342
|
-
host_hash =
|
359
|
+
host_hash = fiobj_hash_string("host", 4);
|
343
360
|
tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, host_hash));
|
344
361
|
pos = tmp.data;
|
345
362
|
while (*pos && *pos != ':')
|
@@ -362,7 +379,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
|
|
362
379
|
{
|
363
380
|
static uint64_t content_length_hash = 0;
|
364
381
|
if (!content_length_hash)
|
365
|
-
content_length_hash =
|
382
|
+
content_length_hash = fiobj_hash_string("content-length", 14);
|
366
383
|
FIOBJ cl = fiobj_hash_get2(h->headers, content_length_hash);
|
367
384
|
if (cl) {
|
368
385
|
tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, content_length_hash));
|
@@ -376,7 +393,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
|
|
376
393
|
{
|
377
394
|
static uint64_t content_type_hash = 0;
|
378
395
|
if (!content_type_hash)
|
379
|
-
content_type_hash =
|
396
|
+
content_type_hash = fiobj_hash_string("content-type", 12);
|
380
397
|
FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash);
|
381
398
|
if (ct) {
|
382
399
|
tmp = fiobj_obj2cstr(ct);
|
@@ -393,10 +410,10 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
|
|
393
410
|
FIOBJ objtmp;
|
394
411
|
static uint64_t xforward_hash = 0;
|
395
412
|
if (!xforward_hash)
|
396
|
-
xforward_hash =
|
413
|
+
xforward_hash = fiobj_hash_string("x-forwarded-proto", 27);
|
397
414
|
static uint64_t forward_hash = 0;
|
398
415
|
if (!forward_hash)
|
399
|
-
forward_hash =
|
416
|
+
forward_hash = fiobj_hash_string("forwarded", 9);
|
400
417
|
if ((objtmp = fiobj_hash_get2(h->headers, xforward_hash))) {
|
401
418
|
tmp = fiobj_obj2cstr(objtmp);
|
402
419
|
if (tmp.len >= 5 && !strncasecmp(tmp.data, "https", 5)) {
|
@@ -432,11 +449,15 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
|
|
432
449
|
}
|
433
450
|
}
|
434
451
|
}
|
452
|
+
} else if (http_settings(h)->tls) {
|
453
|
+
/* no forwarding information, but we do have TLS */
|
454
|
+
rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
|
435
455
|
} else {
|
456
|
+
/* no TLS, no forwarding, assume `http`, which is the default */
|
436
457
|
}
|
437
458
|
}
|
438
459
|
|
439
|
-
/* add all
|
460
|
+
/* add all remaining headers */
|
440
461
|
fiobj_each1(h->headers, 0, iodine_copy2env_task, (void *)env);
|
441
462
|
return env;
|
442
463
|
}
|
@@ -776,213 +797,8 @@ static void on_rack_upgrade(http_s *h, char *proto, size_t len) {
|
|
776
797
|
}
|
777
798
|
|
778
799
|
/* *****************************************************************************
|
779
|
-
|
780
|
-
*****************************************************************************
|
781
|
-
*/
|
782
|
-
|
783
|
-
static void free_iodine_http(http_settings_s *s) {
|
784
|
-
IodineStore.remove((VALUE)s->udata);
|
785
|
-
}
|
786
|
-
|
787
|
-
// clang-format off
|
788
|
-
/**
|
789
|
-
Listens to incoming HTTP connections and handles incoming requests using the
|
790
|
-
Rack specification.
|
791
|
-
|
792
|
-
This is delegated to a lower level C HTTP and Websocket implementation, no
|
793
|
-
Ruby object will be crated except the `env` object required by the Rack
|
794
|
-
specifications.
|
795
|
-
|
796
|
-
Accepts a single Hash argument with the following properties:
|
797
|
-
|
798
|
-
(it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash)
|
799
|
-
|
800
|
-
app:: the Rack application that handles incoming requests. Default: `nil`.
|
801
|
-
port:: the port to listen to. Default: 3000.
|
802
|
-
address:: the address to bind to. Default: binds to all possible addresses.
|
803
|
-
log:: enable response logging (Hijacked sockets aren't logged). Default: off.
|
804
|
-
public:: The root public folder for static file service. Default: none.
|
805
|
-
timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
|
806
|
-
max_body:: The maximum body size for incoming HTTP messages in bytes. Default: ~50Mib.
|
807
|
-
max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib.
|
808
|
-
max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
|
809
|
-
ping:: The Websocket `ping` interval. Default: 40 seconds.
|
810
|
-
|
811
|
-
Either the `app` or the `public` properties are required. If niether exists,
|
812
|
-
the function will fail. If both exist, Iodine will serve static files as well
|
813
|
-
as dynamic requests.
|
814
|
-
|
815
|
-
When using the static file server, it's possible to serve `gzip` versions of
|
816
|
-
the static files by saving a compressed version with the `gz` extension (i.e.
|
817
|
-
`styles.css.gz`).
|
818
|
-
|
819
|
-
`gzip` will only be served to clients tat support the `gzip` transfer
|
820
|
-
encoding.
|
821
|
-
|
822
|
-
Once HTTP/2 is supported (planned, but probably very far away), HTTP/2
|
823
|
-
timeouts will be dynamically managed by Iodine. The `timeout` option is only
|
824
|
-
relevant to HTTP/1.x connections.
|
825
|
-
*/
|
826
|
-
static VALUE iodine_http_listen(VALUE self, VALUE opt) {
|
827
|
-
// clang-format on
|
828
|
-
uint8_t log_http = 0;
|
829
|
-
size_t ping = 0;
|
830
|
-
size_t max_body = 0;
|
831
|
-
size_t max_headers = 0;
|
832
|
-
size_t max_msg = 0;
|
833
|
-
Check_Type(opt, T_HASH);
|
834
|
-
/* copy from deafult hash */
|
835
|
-
/* test arguments */
|
836
|
-
VALUE app = rb_hash_aref(opt, ID2SYM(rb_intern("app")));
|
837
|
-
VALUE www = rb_hash_aref(opt, ID2SYM(rb_intern("public")));
|
838
|
-
VALUE port = rb_hash_aref(opt, ID2SYM(rb_intern("port")));
|
839
|
-
VALUE address = rb_hash_aref(opt, ID2SYM(rb_intern("address")));
|
840
|
-
VALUE tout = rb_hash_aref(opt, ID2SYM(rb_intern("timeout")));
|
841
|
-
if (www == Qnil) {
|
842
|
-
www = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("public")));
|
843
|
-
}
|
844
|
-
if (port == Qnil) {
|
845
|
-
port = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("port")));
|
846
|
-
}
|
847
|
-
if (address == Qnil) {
|
848
|
-
address = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("address")));
|
849
|
-
}
|
850
|
-
if (tout == Qnil) {
|
851
|
-
tout = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("timeout")));
|
852
|
-
}
|
853
|
-
|
854
|
-
VALUE tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_msg")));
|
855
|
-
if (tmp == Qnil) {
|
856
|
-
tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_msg")));
|
857
|
-
}
|
858
|
-
if (tmp != Qnil && tmp != Qfalse) {
|
859
|
-
Check_Type(tmp, T_FIXNUM);
|
860
|
-
max_msg = FIX2ULONG(tmp);
|
861
|
-
}
|
862
|
-
|
863
|
-
tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_body")));
|
864
|
-
if (tmp == Qnil) {
|
865
|
-
tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_body")));
|
866
|
-
}
|
867
|
-
if (tmp != Qnil && tmp != Qfalse) {
|
868
|
-
Check_Type(tmp, T_FIXNUM);
|
869
|
-
max_body = FIX2ULONG(tmp);
|
870
|
-
}
|
871
|
-
tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_headers")));
|
872
|
-
if (tmp == Qnil) {
|
873
|
-
tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_headers")));
|
874
|
-
}
|
875
|
-
if (tmp != Qnil && tmp != Qfalse) {
|
876
|
-
Check_Type(tmp, T_FIXNUM);
|
877
|
-
max_headers = FIX2ULONG(tmp);
|
878
|
-
}
|
879
|
-
|
880
|
-
tmp = rb_hash_aref(opt, ID2SYM(rb_intern("ping")));
|
881
|
-
if (tmp == Qnil) {
|
882
|
-
tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("ping")));
|
883
|
-
}
|
884
|
-
if (tmp != Qnil && tmp != Qfalse) {
|
885
|
-
Check_Type(tmp, T_FIXNUM);
|
886
|
-
ping = FIX2ULONG(tmp);
|
887
|
-
}
|
888
|
-
if (ping > 255) {
|
889
|
-
fprintf(stderr, "Iodine Warning: Websocket timeout value "
|
890
|
-
"is over 255 and will be ignored.\n");
|
891
|
-
ping = 0;
|
892
|
-
}
|
893
|
-
|
894
|
-
tmp = rb_hash_aref(opt, ID2SYM(rb_intern("log")));
|
895
|
-
if (tmp == Qnil) {
|
896
|
-
tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("log")));
|
897
|
-
}
|
898
|
-
if (tmp != Qnil && tmp != Qfalse)
|
899
|
-
log_http = 1;
|
900
|
-
|
901
|
-
if ((app == Qnil || app == Qfalse) && (www == Qnil || www == Qfalse)) {
|
902
|
-
fprintf(stderr, "Iodine Warning: HTTP without application or public folder "
|
903
|
-
"(ignored).\n");
|
904
|
-
return Qfalse;
|
905
|
-
}
|
906
|
-
|
907
|
-
if ((www != Qnil && www != Qfalse)) {
|
908
|
-
Check_Type(www, T_STRING);
|
909
|
-
IodineStore.add(www);
|
910
|
-
rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
|
911
|
-
rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
|
912
|
-
support_xsendfile = 1;
|
913
|
-
} else
|
914
|
-
www = 0;
|
915
|
-
|
916
|
-
if ((address != Qnil && address != Qfalse))
|
917
|
-
Check_Type(address, T_STRING);
|
918
|
-
else
|
919
|
-
address = 0;
|
920
|
-
|
921
|
-
if ((tout != Qnil && tout != Qfalse)) {
|
922
|
-
Check_Type(tout, T_FIXNUM);
|
923
|
-
tout = FIX2ULONG(tout);
|
924
|
-
} else
|
925
|
-
tout = 0;
|
926
|
-
if (tout > 255) {
|
927
|
-
fprintf(stderr, "Iodine Warning: HTTP timeout value "
|
928
|
-
"is over 255 and is silently ignored.\n");
|
929
|
-
tout = 0;
|
930
|
-
}
|
931
|
-
|
932
|
-
if (port != Qnil && port != Qfalse) {
|
933
|
-
if (!RB_TYPE_P(port, T_STRING) && !RB_TYPE_P(port, T_FIXNUM))
|
934
|
-
rb_raise(rb_eTypeError,
|
935
|
-
"The `port` property MUST be either a String or a Number");
|
936
|
-
if (RB_TYPE_P(port, T_FIXNUM))
|
937
|
-
port = rb_funcall2(port, iodine_to_s_method_id, 0, NULL);
|
938
|
-
IodineStore.add(port);
|
939
|
-
} else if (port == Qfalse)
|
940
|
-
port = 0;
|
941
|
-
else if (address &&
|
942
|
-
(StringValueCStr(address)[0] > '9' ||
|
943
|
-
StringValueCStr(address)[0] < '0') &&
|
944
|
-
StringValueCStr(address)[0] != ':' &&
|
945
|
-
(RSTRING_LEN(address) < 3 || StringValueCStr(address)[2] != ':')) {
|
946
|
-
/* address is likely a Unix domain socket address, not an IP address... */
|
947
|
-
port = Qnil;
|
948
|
-
} else {
|
949
|
-
port = rb_str_new("3000", 4);
|
950
|
-
IodineStore.add(port);
|
951
|
-
}
|
952
|
-
|
953
|
-
if ((app != Qnil && app != Qfalse))
|
954
|
-
IodineStore.add(app);
|
955
|
-
else
|
956
|
-
app = 0;
|
957
|
-
|
958
|
-
if (http_listen((port ? StringValueCStr(port) : NULL),
|
959
|
-
(address ? StringValueCStr(address) : NULL),
|
960
|
-
.on_request = on_rack_request, .on_upgrade = on_rack_upgrade,
|
961
|
-
.udata = (void *)app,
|
962
|
-
.timeout = (tout ? FIX2INT(tout) : tout), .ws_timeout = ping,
|
963
|
-
.ws_max_msg_size = max_msg, .max_header_size = max_headers,
|
964
|
-
.on_finish = free_iodine_http, .log = log_http,
|
965
|
-
.max_body_size = max_body,
|
966
|
-
.public_folder = (www ? StringValueCStr(www) : NULL)) == -1) {
|
967
|
-
FIO_LOG_ERROR("Failed to initialize a listening HTTP socket for port %s",
|
968
|
-
port ? StringValueCStr(port) : "3000");
|
969
|
-
rb_raise(rb_eRuntimeError, "Listening socket initialization failed");
|
970
|
-
return Qfalse;
|
971
|
-
}
|
972
|
-
|
973
|
-
if ((app == Qnil || app == Qfalse)) {
|
974
|
-
FIO_LOG_WARNING(
|
975
|
-
"(listen2http) no app, the HTTP service on port %s will only serve "
|
976
|
-
"static files.",
|
977
|
-
(port ? StringValueCStr(port) : "3000"));
|
978
|
-
}
|
979
|
-
if (www) {
|
980
|
-
FIO_LOG_INFO("Serving static files from %s", StringValueCStr(www));
|
981
|
-
}
|
982
|
-
|
983
|
-
return Qtrue;
|
984
|
-
(void)self;
|
985
|
-
}
|
800
|
+
Rack `env` Template Initialization
|
801
|
+
***************************************************************************** */
|
986
802
|
|
987
803
|
static void initialize_env_template(void) {
|
988
804
|
if (env_template_no_upgrade)
|
@@ -1054,19 +870,209 @@ static void initialize_env_template(void) {
|
|
1054
870
|
}
|
1055
871
|
|
1056
872
|
/* *****************************************************************************
|
1057
|
-
|
873
|
+
Listenninng to HTTP
|
874
|
+
*****************************************************************************
|
875
|
+
*/
|
876
|
+
|
877
|
+
static void free_iodine_http(http_settings_s *s) {
|
878
|
+
IodineStore.remove((VALUE)s->udata);
|
879
|
+
}
|
880
|
+
|
881
|
+
// clang-format off
|
882
|
+
/**
|
883
|
+
Listens to incoming HTTP connections and handles incoming requests using the
|
884
|
+
Rack specification.
|
885
|
+
|
886
|
+
This is delegated to a lower level C HTTP and Websocket implementation, no
|
887
|
+
Ruby object will be crated except the `env` object required by the Rack
|
888
|
+
specifications.
|
889
|
+
|
890
|
+
Accepts a single Hash argument with the following properties:
|
891
|
+
|
892
|
+
(it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash)
|
893
|
+
|
894
|
+
app:: the Rack application that handles incoming requests. Default: `nil`.
|
895
|
+
port:: the port to listen to. Default: 3000.
|
896
|
+
address:: the address to bind to. Default: binds to all possible addresses.
|
897
|
+
log:: enable response logging (Hijacked sockets aren't logged). Default: off.
|
898
|
+
public:: The root public folder for static file service. Default: none.
|
899
|
+
timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
|
900
|
+
max_body:: The maximum body size for incoming HTTP messages in bytes. Default: ~50Mib.
|
901
|
+
max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib.
|
902
|
+
max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
|
903
|
+
ping:: The Websocket `ping` interval. Default: 40 seconds.
|
904
|
+
|
905
|
+
Either the `app` or the `public` properties are required. If niether exists,
|
906
|
+
the function will fail. If both exist, Iodine will serve static files as well
|
907
|
+
as dynamic requests.
|
908
|
+
|
909
|
+
When using the static file server, it's possible to serve `gzip` versions of
|
910
|
+
the static files by saving a compressed version with the `gz` extension (i.e.
|
911
|
+
`styles.css.gz`).
|
912
|
+
|
913
|
+
`gzip` will only be served to clients tat support the `gzip` transfer
|
914
|
+
encoding.
|
915
|
+
|
916
|
+
Once HTTP/2 is supported (planned, but probably very far away), HTTP/2
|
917
|
+
timeouts will be dynamically managed by Iodine. The `timeout` option is only
|
918
|
+
relevant to HTTP/1.x connections.
|
919
|
+
*/
|
920
|
+
intptr_t iodine_http_listen(iodine_connection_args_s args){
|
921
|
+
// clang-format on
|
922
|
+
if (args.public.data) {
|
923
|
+
rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
|
924
|
+
rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
|
925
|
+
support_xsendfile = 1;
|
926
|
+
}
|
927
|
+
IodineStore.add(args.handler);
|
928
|
+
intptr_t uuid = http_listen(
|
929
|
+
args.port.data, args.address.data, .on_request = on_rack_request,
|
930
|
+
.on_upgrade = on_rack_upgrade, .udata = (void *)args.handler,
|
931
|
+
.tls = args.tls, .timeout = args.timeout, .ws_timeout = args.ping,
|
932
|
+
.ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers,
|
933
|
+
.on_finish = free_iodine_http, .log = args.log,
|
934
|
+
.max_body_size = args.max_body, .public_folder = args.public.data);
|
935
|
+
if (uuid == -1)
|
936
|
+
return uuid;
|
937
|
+
|
938
|
+
if ((args.handler == Qnil || args.handler == Qfalse)) {
|
939
|
+
FIO_LOG_WARNING("(listen) no handler / app, the HTTP service on port %s "
|
940
|
+
"will only serve "
|
941
|
+
"static files.",
|
942
|
+
args.port.data ? args.port.data : args.address.data);
|
943
|
+
}
|
944
|
+
if (args.public.data) {
|
945
|
+
FIO_LOG_INFO("Serving static files from %s", args.public.data);
|
946
|
+
}
|
947
|
+
|
948
|
+
return uuid;
|
949
|
+
}
|
950
|
+
|
951
|
+
/* *****************************************************************************
|
952
|
+
HTTP Websocket Connect
|
1058
953
|
***************************************************************************** */
|
1059
954
|
|
1060
|
-
|
955
|
+
typedef struct {
|
956
|
+
FIOBJ method;
|
957
|
+
FIOBJ headers;
|
958
|
+
FIOBJ cookies;
|
959
|
+
FIOBJ body;
|
960
|
+
VALUE io;
|
961
|
+
} request_data_s;
|
962
|
+
|
963
|
+
static request_data_s *request_data_create(iodine_connection_args_s *args) {
|
964
|
+
request_data_s *r = fio_malloc(sizeof(*r));
|
965
|
+
FIO_ASSERT_ALLOC(r);
|
966
|
+
VALUE io =
|
967
|
+
iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL,
|
968
|
+
.handler = args->handler, .env = Qnil, .uuid = 0);
|
969
|
+
|
970
|
+
*r = (request_data_s){
|
971
|
+
.method = fiobj_str_new(args->method.data, args->method.len),
|
972
|
+
.headers = fiobj_dup(args->headers),
|
973
|
+
.cookies = fiobj_dup(args->cookies),
|
974
|
+
.body = fiobj_str_new(args->body.data, args->body.len),
|
975
|
+
.io = io,
|
976
|
+
};
|
977
|
+
return r;
|
978
|
+
}
|
979
|
+
|
980
|
+
static void request_data_destroy(request_data_s *r) {
|
981
|
+
fiobj_free(r->method);
|
982
|
+
fiobj_free(r->body);
|
983
|
+
fiobj_free(r->headers);
|
984
|
+
fiobj_free(r->cookies);
|
985
|
+
fio_free(r);
|
986
|
+
}
|
987
|
+
|
988
|
+
static int each_header_ws_client_task(FIOBJ val, void *h_) {
|
989
|
+
http_s *h = h_;
|
990
|
+
FIOBJ key = fiobj_hash_key_in_loop();
|
991
|
+
http_set_header(h, key, fiobj_dup(val));
|
992
|
+
return 0;
|
993
|
+
}
|
994
|
+
static int each_cookie_ws_client_task(FIOBJ val, void *h_) {
|
995
|
+
http_s *h = h_;
|
996
|
+
FIOBJ key = fiobj_hash_key_in_loop();
|
997
|
+
fio_str_info_s n = fiobj_obj2cstr(key);
|
998
|
+
fio_str_info_s v = fiobj_obj2cstr(val);
|
999
|
+
http_set_cookie(h, .name = n.data, .name_len = n.len, .value = v.data,
|
1000
|
+
.value_len = v.len);
|
1001
|
+
return 0;
|
1002
|
+
}
|
1061
1003
|
|
1062
|
-
|
1004
|
+
static void ws_client_http_connected(http_s *h) {
|
1005
|
+
request_data_s *s = h->udata;
|
1006
|
+
if (!s)
|
1007
|
+
return;
|
1008
|
+
h->udata = http_settings(h)->udata = NULL;
|
1009
|
+
if (!h->path) {
|
1010
|
+
h->path = fiobj_str_new("/", 1);
|
1011
|
+
}
|
1012
|
+
/* TODO: add headers and cookies */
|
1013
|
+
fiobj_each1(s->headers, 0, each_header_ws_client_task, h);
|
1014
|
+
fiobj_each1(s->headers, 0, each_cookie_ws_client_task, h);
|
1015
|
+
if (s->io && s->io != Qnil)
|
1016
|
+
http_upgrade2ws(
|
1017
|
+
h, .on_message = iodine_ws_on_message, .on_open = iodine_ws_on_open,
|
1018
|
+
.on_ready = iodine_ws_on_ready, .on_shutdown = iodine_ws_on_shutdown,
|
1019
|
+
.on_close = iodine_ws_on_close, .udata = (void *)s->io);
|
1020
|
+
request_data_destroy(s);
|
1021
|
+
}
|
1063
1022
|
|
1064
|
-
|
1065
|
-
|
1023
|
+
static void ws_client_http_connection_finished(http_settings_s *settings) {
|
1024
|
+
if (!settings)
|
1025
|
+
return;
|
1026
|
+
request_data_s *s = settings->udata;
|
1027
|
+
if (s) {
|
1028
|
+
if (s->io && s->io != Qnil)
|
1029
|
+
iodine_connection_fire_event(s->io, IODINE_CONNECTION_ON_CLOSE, Qnil);
|
1030
|
+
request_data_destroy(s);
|
1031
|
+
}
|
1032
|
+
}
|
1066
1033
|
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1034
|
+
/** Connects to a (remote) WebSocket service. */
|
1035
|
+
intptr_t iodine_ws_connect(iodine_connection_args_s args) {
|
1036
|
+
// http_connect(url, unixaddr, struct http_settings_s)
|
1037
|
+
uint8_t is_unix_socket = 0;
|
1038
|
+
if (memchr(args.address.data, '/', args.address.len)) {
|
1039
|
+
is_unix_socket = 1;
|
1040
|
+
}
|
1041
|
+
FIOBJ url_tmp = FIOBJ_INVALID;
|
1042
|
+
if (!args.url.data) {
|
1043
|
+
url_tmp = fiobj_str_buf(64);
|
1044
|
+
if (args.tls)
|
1045
|
+
fiobj_str_write(url_tmp, "wss://", 6);
|
1046
|
+
else
|
1047
|
+
fiobj_str_write(url_tmp, "ws://", 5);
|
1048
|
+
if (!is_unix_socket) {
|
1049
|
+
fiobj_str_write(url_tmp, args.address.data, args.address.len);
|
1050
|
+
if (args.port.data) {
|
1051
|
+
fiobj_str_write(url_tmp, ":", 1);
|
1052
|
+
fiobj_str_write(url_tmp, args.port.data, args.port.len);
|
1053
|
+
}
|
1054
|
+
}
|
1055
|
+
if (args.path.data)
|
1056
|
+
fiobj_str_write(url_tmp, args.path.data, args.path.len);
|
1057
|
+
else
|
1058
|
+
fiobj_str_write(url_tmp, "/", 1);
|
1059
|
+
args.url = fiobj_obj2cstr(url_tmp);
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
intptr_t uuid = http_connect(
|
1063
|
+
args.url.data, (is_unix_socket ? args.address.data : NULL),
|
1064
|
+
.udata = request_data_create(&args),
|
1065
|
+
.on_response = ws_client_http_connected,
|
1066
|
+
.on_finish = ws_client_http_connection_finished, .tls = args.tls);
|
1067
|
+
fiobj_free(url_tmp);
|
1068
|
+
return uuid;
|
1069
|
+
}
|
1070
|
+
|
1071
|
+
/* *****************************************************************************
|
1072
|
+
Initialization
|
1073
|
+
***************************************************************************** */
|
1074
|
+
|
1075
|
+
void iodine_init_http(void) {
|
1070
1076
|
|
1071
1077
|
rack_autoset(REQUEST_METHOD);
|
1072
1078
|
rack_autoset(PATH_INFO);
|
@@ -1078,6 +1084,14 @@ void iodine_init_http(void) {
|
|
1078
1084
|
rack_autoset(SERVER_PROTOCOL);
|
1079
1085
|
rack_autoset(HTTP_VERSION);
|
1080
1086
|
rack_autoset(REMOTE_ADDR);
|
1087
|
+
|
1088
|
+
rack_autoset(HTTP_ACCEPT);
|
1089
|
+
rack_autoset(HTTP_USER_AGENT);
|
1090
|
+
rack_autoset(HTTP_ACCEPT_ENCODING);
|
1091
|
+
rack_autoset(HTTP_ACCEPT_LANGUAGE);
|
1092
|
+
rack_autoset(HTTP_CONNECTION);
|
1093
|
+
rack_autoset(HTTP_HOST);
|
1094
|
+
|
1081
1095
|
rack_set(HTTP_SCHEME, "http");
|
1082
1096
|
rack_set(HTTPS_SCHEME, "https");
|
1083
1097
|
rack_set(QUERY_ESTRING, "");
|