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