iodine 0.5.1 → 0.5.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +16 -0
- data/ext/iodine/evio_kqueue.c +4 -3
- data/ext/iodine/facil.c +58 -35
- data/ext/iodine/facil.h +12 -1
- data/ext/iodine/fio_mem.c +4 -3
- data/ext/iodine/fio_mem.h +4 -1
- data/ext/iodine/http.c +1 -0
- data/ext/iodine/http.h +7 -4
- data/ext/iodine/http1.c +1 -1
- data/ext/iodine/pubsub.c +22 -0
- data/ext/iodine/pubsub.h +7 -0
- data/ext/iodine/sock.c +1 -1
- data/ext/iodine/sock.h +5 -0
- data/ext/iodine/spnlock.inc +3 -3
- data/ext/iodine/websockets.c +6 -2
- data/lib/iodine/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 360dcbeabc106918078a92d3902fc8ef1a94323c933e028c99224a2ef3b5409a
|
4
|
+
data.tar.gz: 75425229c8fefd9429d0f8d0936186d13668eb7b3f7c8905e16e3d617cdd3390
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f437aad47df8fc727bf360692597e31ad41e297d1ff7dfe46786b9d3d7a9c40ff334e3eeca8cec1927b2143d4509b2ce509eaf192d9d521f4113b0b68a1cec8
|
7
|
+
data.tar.gz: ca852958d4f264f907911f95fbd44288b8e6b525afdfc5708b51d2ea13b3d8e236c166544b19686151fdf2c73741b7353e2c2a90b28702275158f16bdf311eeb
|
data/CHANGELOG.md
CHANGED
@@ -6,7 +6,11 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
-
#### Change log v.0.5.
|
9
|
+
#### Change log v.0.5.2
|
10
|
+
|
11
|
+
**Fix**: fixed compilation issues on FreeBSD. Credit to @adam12 (Adam Daniels) for opening issue #35 and offering a patch.
|
12
|
+
|
13
|
+
#### Change log v.0.5.1
|
10
14
|
|
11
15
|
**Fix**: fixed compilation issues on OS X version < 10.12 and Alpine Linux. Credit to @jdickey (Jeff Dickey) for opening issue #32.
|
12
16
|
|
data/README.md
CHANGED
@@ -27,6 +27,22 @@ Iodine is an **evented** framework with a simple API that builds off the low lev
|
|
27
27
|
|
28
28
|
Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 MRI family, but Rack requires Ruby 2.2.2, and so iodine matches this requirement.
|
29
29
|
|
30
|
+
---
|
31
|
+
|
32
|
+
## Important Note about API Changes and Standardization
|
33
|
+
|
34
|
+
I am very thankful to the many early adopters of iodine using the WebSocket and Pub/Sub API.
|
35
|
+
|
36
|
+
Please note that Iodine 0.5.0's API is a temporary API that was part of an attempt to come up with a de facto Rack standard for WebSocket / SSE connectivity.
|
37
|
+
|
38
|
+
Sadly, this standard isn't progressing as well as I had hoped. More API changes were requested and it seems that the PR with the finalized API is still being considered ([please vote for the PR!](https://github.com/rack/rack/pull/1272)).
|
39
|
+
|
40
|
+
I'm working on Iodine 0.6.0 with the requested updates to the API, along with an updated Pub/Sub API that should fit better with existing pub/sub approaches such ActionCable.
|
41
|
+
|
42
|
+
It's my sincere hope that the upcoming API changes in 0.6.0 will be the last and that this will signal the API's maturity as well as general acceptance.
|
43
|
+
|
44
|
+
---
|
45
|
+
|
30
46
|
## Iodine::Rack == fast & powerful HTTP + Websockets server with native Pub/Sub
|
31
47
|
|
32
48
|
Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the [Websocket draft extension](./SPEC-Websocket-Draft.md).
|
data/ext/iodine/evio_kqueue.c
CHANGED
@@ -12,6 +12,8 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
12
12
|
|
13
13
|
#ifdef EVIO_ENGINE_KQUEUE
|
14
14
|
|
15
|
+
#include <sys/types.h>
|
16
|
+
|
15
17
|
#include <assert.h>
|
16
18
|
#include <errno.h>
|
17
19
|
#include <fcntl.h>
|
@@ -20,13 +22,12 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
20
22
|
#include <stdio.h>
|
21
23
|
#include <stdlib.h>
|
22
24
|
#include <string.h>
|
23
|
-
#include <sys/event.h>
|
24
25
|
#include <sys/socket.h>
|
25
|
-
#include <sys/time.h>
|
26
|
-
#include <sys/types.h>
|
27
26
|
#include <time.h>
|
28
27
|
#include <unistd.h>
|
29
28
|
|
29
|
+
#include <sys/event.h>
|
30
|
+
#include <sys/time.h>
|
30
31
|
/* *****************************************************************************
|
31
32
|
Global data and system independant code
|
32
33
|
***************************************************************************** */
|
data/ext/iodine/facil.c
CHANGED
@@ -1697,7 +1697,9 @@ static void facil_worker_cleanup(void) {
|
|
1697
1697
|
}
|
1698
1698
|
}
|
1699
1699
|
defer_perform();
|
1700
|
-
facil_data->on_finish
|
1700
|
+
if (facil_data->on_finish) {
|
1701
|
+
facil_data->on_finish();
|
1702
|
+
}
|
1701
1703
|
defer_perform();
|
1702
1704
|
evio_close();
|
1703
1705
|
facil_external_cleanup();
|
@@ -1901,56 +1903,77 @@ static inline size_t facil_detect_cpu_cores(void) {
|
|
1901
1903
|
return cpu_count;
|
1902
1904
|
}
|
1903
1905
|
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
if (!
|
1906
|
+
/**
|
1907
|
+
* Returns the number of expected threads / processes to be used by facil.io.
|
1908
|
+
*
|
1909
|
+
* The pointers should start with valid values that match the expected threads /
|
1910
|
+
* processes values passed to `facil_run`.
|
1911
|
+
*
|
1912
|
+
* The data in the pointers will be overwritten with the result.
|
1913
|
+
*/
|
1914
|
+
void facil_expected_concurrency(int16_t *threads, int16_t *processes) {
|
1915
|
+
if (!threads || !processes)
|
1916
|
+
return;
|
1917
|
+
if (!*threads && !*processes) {
|
1914
1918
|
/* both options set to 0 - default to cores*cores matrix */
|
1915
1919
|
ssize_t cpu_count = facil_detect_cpu_cores();
|
1916
1920
|
#if FACIL_CPU_CORES_LIMIT
|
1917
1921
|
if (cpu_count > FACIL_CPU_CORES_LIMIT) {
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1922
|
+
static int print_cores_warning = 1;
|
1923
|
+
if (print_cores_warning) {
|
1924
|
+
fprintf(
|
1925
|
+
stderr,
|
1926
|
+
"INFO: Detected %zu cores. Capping auto-detection of cores "
|
1927
|
+
"to %zu.\n"
|
1928
|
+
" Avoid this message by setting threads / workers manually.\n"
|
1929
|
+
" To increase auto-detection limit, recompile with:\n"
|
1930
|
+
" -DFACIL_CPU_CORES_LIMIT=%zu \n",
|
1931
|
+
(size_t)cpu_count, (size_t)FACIL_CPU_CORES_LIMIT,
|
1932
|
+
(size_t)cpu_count);
|
1933
|
+
print_cores_warning = 0;
|
1934
|
+
}
|
1926
1935
|
cpu_count = FACIL_CPU_CORES_LIMIT;
|
1927
1936
|
}
|
1928
1937
|
#endif
|
1929
|
-
|
1930
|
-
} else if (
|
1938
|
+
*threads = *processes = (int16_t)cpu_count;
|
1939
|
+
} else if (*threads < 0 || *processes < 0) {
|
1931
1940
|
/* Set any option that is less than 0 be equal to cores/value */
|
1932
1941
|
/* Set any option equal to 0 be equal to the other option in value */
|
1933
1942
|
ssize_t cpu_count = facil_detect_cpu_cores();
|
1934
1943
|
|
1935
1944
|
if (cpu_count > 0) {
|
1936
|
-
int16_t
|
1937
|
-
if (
|
1938
|
-
|
1939
|
-
else if (
|
1940
|
-
|
1941
|
-
if (
|
1942
|
-
|
1943
|
-
else if (
|
1944
|
-
|
1945
|
-
|
1945
|
+
int16_t tmp_threads = 0;
|
1946
|
+
if (*threads < 0)
|
1947
|
+
tmp_threads = (int16_t)(cpu_count / (*threads * -1));
|
1948
|
+
else if (*threads == 0)
|
1949
|
+
tmp_threads = -1 * *processes;
|
1950
|
+
if (*processes < 0)
|
1951
|
+
*processes = (int16_t)(cpu_count / (*processes * -1));
|
1952
|
+
else if (*processes == 0)
|
1953
|
+
*processes = -1 * *threads;
|
1954
|
+
*threads = tmp_threads;
|
1946
1955
|
}
|
1947
1956
|
}
|
1948
1957
|
|
1949
1958
|
/* make sure we have at least one process and at least one thread */
|
1950
|
-
if (
|
1951
|
-
|
1952
|
-
if (
|
1953
|
-
|
1959
|
+
if (*processes <= 0)
|
1960
|
+
*processes = 1;
|
1961
|
+
if (*threads <= 0)
|
1962
|
+
*threads = 1;
|
1963
|
+
}
|
1964
|
+
|
1965
|
+
#undef facil_run
|
1966
|
+
void facil_run(struct facil_run_args args) {
|
1967
|
+
signal(SIGPIPE, SIG_IGN);
|
1968
|
+
if (!facil_data)
|
1969
|
+
facil_lib_init();
|
1970
|
+
if (!args.on_idle)
|
1971
|
+
args.on_idle = mock_idle;
|
1972
|
+
if (!args.on_finish)
|
1973
|
+
args.on_finish = mock_idle;
|
1974
|
+
|
1975
|
+
/* compute actual concurrency */
|
1976
|
+
facil_expected_concurrency(&args.threads, &args.processes);
|
1954
1977
|
|
1955
1978
|
/* listen to SIGINT / SIGTERM */
|
1956
1979
|
facil_setup_signal_handler();
|
data/ext/iodine/facil.h
CHANGED
@@ -121,7 +121,7 @@ struct FacilIOProtocol {
|
|
121
121
|
* Called when the server is shutting down, immediately before closing the
|
122
122
|
* connection.
|
123
123
|
*
|
124
|
-
* The callback runs within a {
|
124
|
+
* The callback runs within a {FIO_PR_LOCK_TASK} lock, so it will never run
|
125
125
|
* concurrently wil {on_data} or other connection specific tasks.
|
126
126
|
*/
|
127
127
|
void (*on_shutdown)(intptr_t uuid, protocol_s *protocol);
|
@@ -336,6 +336,7 @@ struct facil_run_args {
|
|
336
336
|
/** called when the server is done, to clean up any leftovers. */
|
337
337
|
void (*on_finish)(void);
|
338
338
|
};
|
339
|
+
|
339
340
|
/**
|
340
341
|
* Starts the facil.io event loop. This function will return after facil.io is
|
341
342
|
* done (after shutdown).
|
@@ -345,6 +346,16 @@ struct facil_run_args {
|
|
345
346
|
void facil_run(struct facil_run_args args);
|
346
347
|
#define facil_run(...) facil_run((struct facil_run_args){__VA_ARGS__})
|
347
348
|
|
349
|
+
/**
|
350
|
+
* Returns the number of expected threads / processes to be used by facil.io.
|
351
|
+
*
|
352
|
+
* The pointers should start with valid values that match the expected threads /
|
353
|
+
* processes values passed to `facil_run`.
|
354
|
+
*
|
355
|
+
* The data in the pointers will be overwritten with the result.
|
356
|
+
*/
|
357
|
+
void facil_expected_concurrency(int16_t *threads, int16_t *processes);
|
358
|
+
|
348
359
|
/**
|
349
360
|
* returns true (1) if the facil.io engine is already running.
|
350
361
|
*/
|
data/ext/iodine/fio_mem.c
CHANGED
@@ -140,9 +140,10 @@ static inline void *sys_alloc(size_t len, uint8_t is_indi) {
|
|
140
140
|
static void *next_alloc = NULL;
|
141
141
|
/* hope for the best? */
|
142
142
|
#ifdef MAP_ALIGNED
|
143
|
-
result =
|
144
|
-
next_alloc, len, PROT_READ | PROT_WRITE,
|
145
|
-
|
143
|
+
result =
|
144
|
+
mmap(next_alloc, len, PROT_READ | PROT_WRITE,
|
145
|
+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED(FIO_MEMORY_BLOCK_SIZE_LOG),
|
146
|
+
-1, 0);
|
146
147
|
#else
|
147
148
|
result = mmap(next_alloc, len, PROT_READ | PROT_WRITE,
|
148
149
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
data/ext/iodine/fio_mem.h
CHANGED
@@ -122,8 +122,11 @@ void fio_malloc_test(void);
|
|
122
122
|
#endif
|
123
123
|
|
124
124
|
/** Allocator default settings. */
|
125
|
+
#ifndef FIO_MEMORY_BLOCK_SIZE_LOG
|
126
|
+
#define FIO_MEMORY_BLOCK_SIZE_LOG (17) /* 17 == 128Kb */
|
127
|
+
#endif
|
125
128
|
#ifndef FIO_MEMORY_BLOCK_SIZE
|
126
|
-
#define FIO_MEMORY_BLOCK_SIZE ((uintptr_t)1 <<
|
129
|
+
#define FIO_MEMORY_BLOCK_SIZE ((uintptr_t)1 << FIO_MEMORY_BLOCK_SIZE_LOG)
|
127
130
|
#endif
|
128
131
|
#ifndef FIO_MEMORY_BLOCK_MASK
|
129
132
|
#define FIO_MEMORY_BLOCK_MASK (FIO_MEMORY_BLOCK_SIZE - 1) /* 0b111... */
|
data/ext/iodine/http.c
CHANGED
@@ -2289,6 +2289,7 @@ size_t http_date2rfc2109(char *target, struct tm *tmbuf) {
|
|
2289
2289
|
pos[0] = MONTH_NAMES[tmbuf->tm_mon][0];
|
2290
2290
|
pos[1] = MONTH_NAMES[tmbuf->tm_mon][1];
|
2291
2291
|
pos[2] = MONTH_NAMES[tmbuf->tm_mon][2];
|
2292
|
+
pos[3] = ' ';
|
2292
2293
|
pos += 4;
|
2293
2294
|
// write year.
|
2294
2295
|
pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10);
|
data/ext/iodine/http.h
CHANGED
@@ -449,14 +449,17 @@ sock_peer_addr_s http_peer_addr(http_s *h);
|
|
449
449
|
* It's possible to hijack the socket and than reconnect it to a new protocol
|
450
450
|
* object.
|
451
451
|
*
|
452
|
-
*
|
453
|
-
*
|
452
|
+
* It's possible to call `http_finish` immediately after calling `http_hijack`
|
453
|
+
* in order to send the outgoing headers.
|
454
|
+
*
|
455
|
+
* If any aditional HTTP functions are called after the hijacking, the protocol
|
456
|
+
* object might attempt to continue reading data from the buffer.
|
454
457
|
*
|
455
458
|
* Returns the underlining socket connection's uuid. If `leftover` isn't NULL,
|
456
459
|
* it will be populated with any remaining data in the HTTP buffer (the data
|
457
|
-
* will be automatically deallocated, so copy the data
|
460
|
+
* will be automatically deallocated, so copy the data when in need).
|
458
461
|
*
|
459
|
-
* WARNING: this isn't a good
|
462
|
+
* WARNING: this isn't a good way to handle HTTP connections, especially as
|
460
463
|
* HTTP/2 enters the picture.
|
461
464
|
*/
|
462
465
|
intptr_t http_hijack(http_s *h, fio_cstr_s *leftover);
|
data/ext/iodine/http1.c
CHANGED
data/ext/iodine/pubsub.c
CHANGED
@@ -332,6 +332,15 @@ pubsub_sub_pt pubsub_find_sub(struct pubsub_subscribe_args args) {
|
|
332
332
|
#define pubsub_find_sub(...) \
|
333
333
|
pubsub_find_sub((struct pubsub_subscribe_args){__VA_ARGS__})
|
334
334
|
|
335
|
+
/**
|
336
|
+
* This helper returns a temporary handle to an existing subscription's channel.
|
337
|
+
*
|
338
|
+
* To keep the handle beyond the lifetime of the subscription, use `fiobj_dup`.
|
339
|
+
*/
|
340
|
+
FIOBJ pubsub_sub_channel(pubsub_sub_pt sub) {
|
341
|
+
return (((channel_s *)((client_s *)sub)->parent))->name;
|
342
|
+
}
|
343
|
+
|
335
344
|
/**
|
336
345
|
* Unsubscribes from a specific subscription.
|
337
346
|
*
|
@@ -406,11 +415,24 @@ static void pubsub_on_channel_destroy(channel_s *ch) {
|
|
406
415
|
|
407
416
|
/** Registers an engine, so it's callback can be called. */
|
408
417
|
void pubsub_engine_register(pubsub_engine_s *engine) {
|
418
|
+
if (!engine) {
|
419
|
+
return;
|
420
|
+
}
|
409
421
|
spn_lock(&engn_lock);
|
410
422
|
fio_hash_insert(
|
411
423
|
&engines,
|
412
424
|
(fio_hash_key_s){.hash = (uintptr_t)engine, .obj = FIOBJ_INVALID},
|
413
425
|
engine);
|
426
|
+
if (engine->subscribe) {
|
427
|
+
FIO_HASH_FOR_LOOP(&channels, i) {
|
428
|
+
channel_s *ch = i->obj;
|
429
|
+
engine->subscribe(engine, ch->name, 0);
|
430
|
+
}
|
431
|
+
FIO_HASH_FOR_LOOP(&patterns, i) {
|
432
|
+
channel_s *ch = i->obj;
|
433
|
+
engine->subscribe(engine, ch->name, 1);
|
434
|
+
}
|
435
|
+
}
|
414
436
|
spn_unlock(&engn_lock);
|
415
437
|
}
|
416
438
|
|
data/ext/iodine/pubsub.h
CHANGED
@@ -108,6 +108,13 @@ pubsub_sub_pt pubsub_find_sub(struct pubsub_subscribe_args);
|
|
108
108
|
#define pubsub_find_sub(...) \
|
109
109
|
pubsub_find_sub((struct pubsub_subscribe_args){__VA_ARGS__})
|
110
110
|
|
111
|
+
/**
|
112
|
+
* This helper returns a temporary handle to an existing subscription's channel.
|
113
|
+
*
|
114
|
+
* To keep the handle beyond the lifetime of the subscription, use `fiobj_dup`.
|
115
|
+
*/
|
116
|
+
FIOBJ pubsub_sub_channel(pubsub_sub_pt);
|
117
|
+
|
111
118
|
/**
|
112
119
|
* Unsubscribes from a specific subscription.
|
113
120
|
*
|
data/ext/iodine/sock.c
CHANGED
@@ -1090,7 +1090,7 @@ ssize_t sock_write2_fn(sock_write_info_s options) {
|
|
1090
1090
|
if (validate_uuid(options.uuid) || !options.buffer)
|
1091
1091
|
goto error;
|
1092
1092
|
lock_fd(fd);
|
1093
|
-
if (!fdinfo(fd).open) {
|
1093
|
+
if (!fdinfo(fd).open || fdinfo(fd).close) {
|
1094
1094
|
unlock_fd(fd);
|
1095
1095
|
goto error;
|
1096
1096
|
}
|
data/ext/iodine/sock.h
CHANGED
@@ -39,6 +39,11 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
39
39
|
#include <sys/types.h>
|
40
40
|
#include <unistd.h>
|
41
41
|
|
42
|
+
#if defined(__FreeBSD__)
|
43
|
+
#include <netinet/in.h>
|
44
|
+
#include <sys/socket.h>
|
45
|
+
#endif
|
46
|
+
|
42
47
|
// clang-format off
|
43
48
|
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
|
44
49
|
# if defined(__has_include)
|
data/ext/iodine/spnlock.inc
CHANGED
@@ -81,10 +81,10 @@ static inline int spn_trylock(spn_lock_i *lock) {
|
|
81
81
|
}
|
82
82
|
|
83
83
|
/** Releases a lock. */
|
84
|
-
static inline __attribute__((unused))
|
85
|
-
|
86
|
-
*lock = 0;
|
84
|
+
static inline __attribute__((unused)) int spn_unlock(spn_lock_i *lock) {
|
85
|
+
return SPN_LOCK_BUILTIN(lock, 0);
|
87
86
|
}
|
87
|
+
|
88
88
|
/** returns a lock's state (non 0 == Busy). */
|
89
89
|
static inline __attribute__((unused)) int spn_is_locked(spn_lock_i *lock) {
|
90
90
|
__asm__ volatile("" ::: "memory");
|
data/ext/iodine/websockets.c
CHANGED
@@ -9,6 +9,7 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
9
9
|
#include "fio_llist.h"
|
10
10
|
#include "fiobj.h"
|
11
11
|
|
12
|
+
#include "evio.h"
|
12
13
|
#include "fio_base64.h"
|
13
14
|
#include "fio_sha1.h"
|
14
15
|
#include "http.h"
|
@@ -286,10 +287,13 @@ static void on_data_first(intptr_t sockfd, protocol_s *ws_) {
|
|
286
287
|
if (ws->on_open)
|
287
288
|
ws->on_open(ws);
|
288
289
|
ws->protocol.on_data = on_data;
|
290
|
+
ws->protocol.on_ready = on_ready;
|
289
291
|
|
290
|
-
if (ws->length)
|
292
|
+
if (ws->length) {
|
291
293
|
ws->length = websocket_consume(ws->buffer.data, ws->length, ws,
|
292
294
|
(~(ws->is_client) & 1));
|
295
|
+
}
|
296
|
+
evio_add_write(sock_uuid2fd(sockfd), (void *)sockfd);
|
293
297
|
|
294
298
|
facil_force_event(sockfd, FIO_EVENT_ON_DATA);
|
295
299
|
}
|
@@ -310,7 +314,7 @@ static ws_s *new_websocket(intptr_t uuid) {
|
|
310
314
|
.protocol.ping = ws_ping,
|
311
315
|
.protocol.on_data = on_data_first,
|
312
316
|
.protocol.on_close = on_close,
|
313
|
-
.protocol.on_ready =
|
317
|
+
.protocol.on_ready = NULL /* filled in after `on_open` */,
|
314
318
|
.protocol.on_shutdown = on_shutdown,
|
315
319
|
.subscriptions = FIO_LS_INIT(ws->subscriptions),
|
316
320
|
.is_client = 0,
|
data/lib/iodine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iodine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|