tipi 0.40 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,10 +9,19 @@ opts = {
9
9
  }
10
10
 
11
11
  puts "pid: #{Process.pid}"
12
- puts 'Listening on port 4411...'
12
+ puts 'Listening on port 10080...'
13
+
14
+ # GC.disable
15
+ # Thread.current.backend.idle_gc_period = 60
16
+
17
+ spin_loop(interval: 10) { p Thread.current.fiber_scheduling_stats }
18
+
19
+ spin_loop(interval: 10) do
20
+ GC.compact
21
+ end
13
22
 
14
23
  spin do
15
- Tipi.serve('0.0.0.0', 4411, opts) do |req|
24
+ Tipi.serve('0.0.0.0', 10080, opts) do |req|
16
25
  if req.path == '/stream'
17
26
  req.send_headers('Foo' => 'Bar')
18
27
  sleep 1
@@ -23,7 +32,7 @@ spin do
23
32
  else
24
33
  req.respond("Hello world!\n")
25
34
  end
26
- p req.transfer_counts
35
+ # p req.transfer_counts
27
36
  end
28
37
  p 'done...'
29
38
  end.await
@@ -16,23 +16,11 @@ root_path = FileUtils.pwd
16
16
 
17
17
  trap('INT') { exit! }
18
18
 
19
- app = Tipi.route do |req|
20
- req.on('normal') do
21
- path = File.join(root_path, req.route_relative_path)
22
- if File.file?(path)
23
- req.serve_file(path)
24
- else
25
- req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
26
- end
27
- end
28
- req.on('spliced') do
29
- path = File.join(root_path, req.route_relative_path)
30
- if File.file?(path)
31
- req.serve_file(path, respond_from_io: true)
32
- else
33
- req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
34
- end
19
+ Tipi.serve('0.0.0.0', 4411, opts) do |req|
20
+ path = File.join(root_path, req.path)
21
+ if File.file?(path)
22
+ req.serve_file(path)
23
+ else
24
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
35
25
  end
36
26
  end
37
-
38
- Tipi.serve('0.0.0.0', 4411, opts, &app)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'mkmf'
5
+
6
+ $CFLAGS << " -Wno-pointer-arith"
7
+
8
+ CONFIG['optflags'] << ' -fno-strict-aliasing' unless RUBY_PLATFORM =~ /mswin/
9
+
10
+
11
+ dir_config 'tipi_ext'
12
+ create_makefile 'tipi_ext'
@@ -0,0 +1,534 @@
1
+ #include "ruby.h"
2
+ #include "http1_parser.h"
3
+
4
+ #define str_downcase(str) (rb_funcall((str), ID_downcase, 0))
5
+
6
+ const int MAX_METHOD_LENGTH = 16;
7
+ const int MAX_PATH_LENGTH = 1024;
8
+ const int MAX_HEADER_KEY_LENGTH = 128;
9
+ const int MAX_HEADER_VALUE_LENGTH = 2048;
10
+
11
+ ID ID_backend_read;
12
+ ID ID_downcase;
13
+ ID ID_read;
14
+ ID ID_readpartial;
15
+ ID ID_to_i;
16
+
17
+ VALUE mPolyphony;
18
+ VALUE cError;
19
+
20
+ VALUE MAX_READ_LENGTH;
21
+ VALUE BUFFER_END;
22
+
23
+ VALUE STR_pseudo_method;
24
+ VALUE STR_pseudo_path;
25
+ VALUE STR_pseudo_protocol;
26
+
27
+ VALUE STR_content_length;
28
+ VALUE STR_transfer_encoding;
29
+
30
+ typedef struct parser {
31
+ VALUE io;
32
+ VALUE buffer;
33
+ int pos;
34
+ } Parser_t;
35
+
36
+ VALUE cParser = Qnil;
37
+
38
+ static void Parser_mark(void *ptr) {
39
+ Parser_t *parser = ptr;
40
+ rb_gc_mark(parser->io);
41
+ rb_gc_mark(parser->buffer);
42
+ }
43
+
44
+ static void Parser_free(void *ptr) {
45
+ Parser_t *parser = ptr;
46
+ xfree(ptr);
47
+ }
48
+
49
+ static size_t Parser_size(const void *ptr) {
50
+ return sizeof(Parser_t);
51
+ }
52
+
53
+ static const rb_data_type_t Parser_type = {
54
+ "Parser",
55
+ {Parser_mark, Parser_free, Parser_size,},
56
+ 0, 0, 0
57
+ };
58
+
59
+ static VALUE Parser_allocate(VALUE klass) {
60
+ Parser_t *parser;
61
+
62
+ parser = ALLOC(Parser_t);
63
+ return TypedData_Wrap_Struct(klass, &Parser_type, parser);
64
+ }
65
+
66
+ #define GetParser(obj, parser) \
67
+ TypedData_Get_Struct((obj), Parser_t, &Parser_type, (parser))
68
+
69
+ VALUE Parser_initialize(VALUE self, VALUE io) {
70
+ Parser_t *parser;
71
+ GetParser(self, parser);
72
+
73
+ parser->io = io;
74
+ parser->buffer = rb_str_new_literal("");
75
+ parser->pos = 0;
76
+
77
+ return self;
78
+ }
79
+
80
+ ////////////////////////////////////////////////////////////////////////////////
81
+ ////////////////////////////////////////////////////////////////////////////////
82
+ ////////////////////////////////////////////////////////////////////////////////
83
+
84
+ struct parser_state {
85
+ struct parser *parser;
86
+ char *ptr;
87
+ int len;
88
+ };
89
+
90
+ #define READ_CONCAT(io, buffer, len) \
91
+ rb_funcall(io, ID_readpartial, 4, len, buffer, BUFFER_END, Qfalse)
92
+
93
+ static inline int fill_buffer(struct parser_state *state) {
94
+ READ_CONCAT(state->parser->io, state->parser->buffer, MAX_READ_LENGTH);
95
+ int len = RSTRING_LEN(state->parser->buffer);
96
+ int read_bytes = len - state->len;
97
+ if (!read_bytes) return 0;
98
+
99
+ state->ptr = RSTRING_PTR(state->parser->buffer);
100
+ state->len = len;
101
+ return read_bytes;
102
+ }
103
+
104
+ #define FILL_BUFFER_OR_GOTO_EOF(state) { if (!fill_buffer(state)) goto eof; }
105
+
106
+ #define BUFFER_POS(state) ((state)->parser->pos)
107
+ #define BUFFER_LEN(state) ((state)->len)
108
+ #define BUFFER_CUR(state) ((state)->ptr[(state)->parser->pos])
109
+ #define BUFFER_AT(state, pos) ((state)->ptr[pos])
110
+ #define BUFFER_STR(state, pos, len) (rb_utf8_str_new((state)->ptr + pos, len))
111
+
112
+ #define INC_BUFFER_POS(state) { \
113
+ BUFFER_POS(state)++; \
114
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
115
+ }
116
+
117
+ #define INC_BUFFER_POS_NO_READ(state) BUFFER_POS(state)++;
118
+
119
+ #define INC_BUFFER_POS_UTF8(state, len) { \
120
+ unsigned char c = BUFFER_CUR(state); \
121
+ if ((c & 0xf0) == 0xf0) { \
122
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 4) FILL_BUFFER_OR_GOTO_EOF(state); \
123
+ BUFFER_POS(state) += 4; \
124
+ len += 4; \
125
+ } \
126
+ else if ((c & 0xe0) == 0xe0) { \
127
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 3) FILL_BUFFER_OR_GOTO_EOF(state); \
128
+ BUFFER_POS(state) += 3; \
129
+ len += 3; \
130
+ } \
131
+ else if ((c & 0xc0) == 0xc0) { \
132
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 2) FILL_BUFFER_OR_GOTO_EOF(state); \
133
+ BUFFER_POS(state) += 2; \
134
+ len += 2; \
135
+ } \
136
+ else { \
137
+ BUFFER_POS(state)++; \
138
+ len ++; \
139
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
140
+ } \
141
+ }
142
+
143
+ #define INIT_PARSER_STATE(state) { \
144
+ (state)->len = RSTRING_LEN((state)->parser->buffer); \
145
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) \
146
+ FILL_BUFFER_OR_GOTO_EOF(state) \
147
+ else \
148
+ (state)->ptr = RSTRING_PTR((state)->parser->buffer); \
149
+ }
150
+
151
+ #define RAISE_BAD_REQUEST(msg) rb_raise(cError, msg)
152
+
153
+ #define SET_HEADER_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
154
+ VALUE value = BUFFER_STR(state, pos, len); \
155
+ rb_hash_aset(headers, key, value); \
156
+ RB_GC_GUARD(value); \
157
+ }
158
+
159
+ #define SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
160
+ VALUE value = str_downcase(BUFFER_STR(state, pos, len)); \
161
+ rb_hash_aset(headers, key, value); \
162
+ RB_GC_GUARD(value); \
163
+ }
164
+
165
+ #define BUFFER_TRIM_MIN_LEN 4096
166
+ #define BUFFER_TRIM_MIN_POS 2048
167
+
168
+ static inline void buffer_trim(struct parser_state *state) {
169
+ int len = RSTRING_LEN(state->parser->buffer);
170
+ int pos = state->parser->pos;
171
+ int left = len - pos;
172
+
173
+ // The buffer is trimmed only if length and position thresholds are passed,
174
+ // *and* position is past the halfway point.
175
+ if (len < BUFFER_TRIM_MIN_LEN ||
176
+ pos < BUFFER_TRIM_MIN_POS ||
177
+ left >= pos) return;
178
+
179
+ if (left > 0) {
180
+ char *ptr = RSTRING_PTR(state->parser->buffer);
181
+ memcpy(ptr, ptr + pos, left);
182
+ }
183
+ rb_str_set_len(state->parser->buffer, left);
184
+ state->parser->pos = 0;
185
+ }
186
+
187
+ ////////////////////////////////////////////////////////////////////////////////
188
+ ////////////////////////////////////////////////////////////////////////////////
189
+ ////////////////////////////////////////////////////////////////////////////////
190
+
191
+ static inline int parse_method(struct parser_state *state, VALUE headers) {
192
+ int pos = BUFFER_POS(state);
193
+ int len = 0;
194
+
195
+ loop:
196
+ switch (BUFFER_CUR(state)) {
197
+ case ' ':
198
+ if (len < 1 || len > MAX_METHOD_LENGTH) goto bad_request;
199
+ INC_BUFFER_POS(state);
200
+ goto done;
201
+ case '\r':
202
+ case '\n':
203
+ goto bad_request;
204
+ default:
205
+ INC_BUFFER_POS_UTF8(state, len);
206
+ if (len >= MAX_METHOD_LENGTH) goto bad_request;
207
+ goto loop;
208
+ }
209
+ done:
210
+ SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_method, pos, len);
211
+ return 1;
212
+ bad_request:
213
+ RAISE_BAD_REQUEST("Invalid method");
214
+ eof:
215
+ return 0;
216
+ }
217
+
218
+ static int parse_path(struct parser_state *state, VALUE headers) {
219
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
220
+ int pos = BUFFER_POS(state);
221
+ int len = 0;
222
+ loop:
223
+ switch (BUFFER_CUR(state)) {
224
+ case ' ':
225
+ if (len < 1 || len > MAX_PATH_LENGTH) goto bad_request;
226
+ INC_BUFFER_POS(state);
227
+ goto done;
228
+ case '\r':
229
+ case '\n':
230
+ goto bad_request;
231
+ default:
232
+ INC_BUFFER_POS_UTF8(state, len);
233
+ if (len >= MAX_PATH_LENGTH) goto bad_request;
234
+ goto loop;
235
+ }
236
+ done:
237
+ SET_HEADER_VALUE_FROM_BUFFER(state, headers, STR_pseudo_path, pos, len);
238
+ return 1;
239
+ bad_request:
240
+ RAISE_BAD_REQUEST("Invalid path");
241
+ eof:
242
+ return 0;
243
+ }
244
+
245
+ // case-insensitive compare
246
+ #define CMP_CI(state, down, up) ((BUFFER_CUR(state) == down) || (BUFFER_CUR(state) == up))
247
+
248
+ static int parse_protocol(struct parser_state *state, VALUE headers) {
249
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
250
+ int pos = BUFFER_POS(state);
251
+ int len = 0;
252
+
253
+ if (CMP_CI(state, 'H', 'h')) INC_BUFFER_POS(state) else goto bad_request;
254
+ if (CMP_CI(state, 'T', 't')) INC_BUFFER_POS(state) else goto bad_request;
255
+ if (CMP_CI(state, 'T', 't')) INC_BUFFER_POS(state) else goto bad_request;
256
+ if (CMP_CI(state, 'P', 'p')) INC_BUFFER_POS(state) else goto bad_request;
257
+ if (BUFFER_CUR(state) == '/') INC_BUFFER_POS(state) else goto bad_request;
258
+ if (BUFFER_CUR(state) == '1') INC_BUFFER_POS(state) else goto bad_request;
259
+ len = 6;
260
+ loop:
261
+ switch (BUFFER_CUR(state)) {
262
+ case '\r':
263
+ INC_BUFFER_POS(state);
264
+ goto eol;
265
+ case '\n':
266
+ INC_BUFFER_POS(state);
267
+ goto done;
268
+ case '.':
269
+ case '1':
270
+ len++;
271
+ if (len > 8) goto bad_request;
272
+ INC_BUFFER_POS(state);
273
+ goto loop;
274
+ default:
275
+ goto bad_request;
276
+ }
277
+ eol:
278
+ if (BUFFER_CUR(state) != '\n') goto bad_request;
279
+ INC_BUFFER_POS(state);
280
+ done:
281
+ if (len < 6 || len > 8) goto bad_request;
282
+ SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_protocol, pos, len);
283
+ return 1;
284
+ bad_request:
285
+ RAISE_BAD_REQUEST("Invalid protocol");
286
+ eof:
287
+ return 0;
288
+ }
289
+
290
+ int parse_request_line(struct parser_state *state, VALUE headers) {
291
+ if (!parse_method(state, headers)) goto eof;
292
+ if (!parse_path(state, headers)) goto eof;
293
+ if (!parse_protocol(state, headers)) goto eof;
294
+
295
+ return 1;
296
+ eof:
297
+ return 0;
298
+ }
299
+
300
+ static inline int parse_header_key(struct parser_state *state, VALUE *key) {
301
+ int pos = BUFFER_POS(state);
302
+ int len = 0;
303
+
304
+ loop:
305
+ switch (BUFFER_CUR(state)) {
306
+ case ':':
307
+ if (len < 1 || len > MAX_HEADER_KEY_LENGTH)
308
+ goto bad_request;
309
+ INC_BUFFER_POS(state);
310
+ goto done;
311
+ case '\r':
312
+ if (BUFFER_POS(state) > pos) goto bad_request;
313
+
314
+ INC_BUFFER_POS(state);
315
+ goto eol;
316
+ case '\n':
317
+ if (BUFFER_POS(state) > pos) goto bad_request;
318
+
319
+ INC_BUFFER_POS_NO_READ(state);
320
+ goto done;
321
+ default:
322
+ INC_BUFFER_POS_UTF8(state, len);
323
+ if (len >= MAX_HEADER_KEY_LENGTH) goto bad_request;
324
+ goto loop;
325
+ }
326
+ eol:
327
+ if (BUFFER_CUR(state) != '\n') goto bad_request;
328
+ INC_BUFFER_POS_NO_READ(state);
329
+ done:
330
+ if (len == 0) return -1;
331
+ (*key) = str_downcase(BUFFER_STR(state, pos, len));
332
+ return 1;
333
+ bad_request:
334
+ RAISE_BAD_REQUEST("Invalid header key");
335
+ eof:
336
+ return 0;
337
+ }
338
+
339
+ static inline int parse_header_value(struct parser_state *state, VALUE *value) {
340
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
341
+
342
+ int pos = BUFFER_POS(state);
343
+ int len = 0;
344
+
345
+ loop:
346
+ switch (BUFFER_CUR(state)) {
347
+ case '\r':
348
+ INC_BUFFER_POS(state);
349
+ goto eol;
350
+ case '\n':
351
+ goto done;
352
+ default:
353
+ INC_BUFFER_POS_UTF8(state, len);
354
+ if (len >= MAX_HEADER_VALUE_LENGTH) goto bad_request;
355
+ goto loop;
356
+ }
357
+ eol:
358
+ if (BUFFER_CUR(state) != '\n') goto bad_request;
359
+ INC_BUFFER_POS(state);
360
+ done:
361
+ if (len < 1 || len > MAX_HEADER_VALUE_LENGTH) goto bad_request;
362
+ (*value) = BUFFER_STR(state, pos, len);
363
+ return 1;
364
+ bad_request:
365
+ RAISE_BAD_REQUEST("Invalid header value");
366
+ eof:
367
+ return 0;
368
+ }
369
+
370
+ static inline int parse_header(struct parser_state *state, VALUE headers) {
371
+ VALUE key, value;
372
+
373
+ switch (parse_header_key(state, &key)) {
374
+ case -1: return -1;
375
+ case 0: goto eof;
376
+ }
377
+
378
+ if (!parse_header_value(state, &value)) goto eof;
379
+
380
+ VALUE existing = rb_hash_aref(headers, key);
381
+ if (existing != Qnil) {
382
+ if (TYPE(existing) != T_ARRAY) {
383
+ existing = rb_ary_new3(2, existing, value);
384
+ rb_hash_aset(headers, key, existing);
385
+ }
386
+ else
387
+ rb_ary_push(existing, value);
388
+ }
389
+ else
390
+ rb_hash_aset(headers, key, value);
391
+
392
+ RB_GC_GUARD(existing);
393
+ RB_GC_GUARD(key);
394
+ RB_GC_GUARD(value);
395
+ return 1;
396
+ eof:
397
+ return 0;
398
+ }
399
+
400
+ VALUE Parser_parse_headers(VALUE self) {
401
+ struct parser_state state;
402
+ GetParser(self, state.parser);
403
+ VALUE headers = rb_hash_new();
404
+
405
+ buffer_trim(&state);
406
+ INIT_PARSER_STATE(&state);
407
+
408
+ if (!parse_request_line(&state, headers)) goto eof;
409
+
410
+ loop:
411
+ switch (parse_header(&state, headers)) {
412
+ case -1: goto done; // empty header => end of headers
413
+ case 0: goto eof;
414
+ default: goto loop;
415
+ }
416
+
417
+ done:
418
+ RB_GC_GUARD(headers);
419
+ return headers;
420
+ eof:
421
+ return Qnil;
422
+ }
423
+
424
+ ////////////////////////////////////////////////////////////////////////////////
425
+ ////////////////////////////////////////////////////////////////////////////////
426
+ ////////////////////////////////////////////////////////////////////////////////
427
+
428
+ const int READ_MAX_LEN = 1 << 20;
429
+
430
+ static inline int parse_content_length(VALUE value) {
431
+ VALUE to_i = rb_funcall(value, ID_to_i, 0);
432
+ return NUM2INT(to_i);
433
+ }
434
+
435
+ VALUE read_body_with_content_length(VALUE self, int content_length) {
436
+ if (content_length < 0) return Qnil;
437
+
438
+ Parser_t *parser;
439
+ GetParser(self, parser);
440
+
441
+ VALUE body;
442
+
443
+ int len = RSTRING_LEN(parser->buffer);
444
+ int pos = parser->pos;
445
+ int left = content_length;
446
+
447
+ if (pos < len) {
448
+ int available = len - pos;
449
+ if (available > content_length) available = content_length;
450
+ body = rb_str_new(RSTRING_PTR(parser->buffer) + pos, available);
451
+ parser->pos += available;
452
+ left -= available;
453
+ len = available;
454
+ }
455
+ else {
456
+ body = Qnil;
457
+ len = 0;
458
+ }
459
+
460
+ while (left) {
461
+ int maxlen = left <= READ_MAX_LEN ? left : READ_MAX_LEN;
462
+
463
+ if (body != Qnil) {
464
+ VALUE tmp_buf = rb_funcall(
465
+ mPolyphony, ID_backend_read, 5, parser->io, Qnil, INT2NUM(maxlen), Qfalse, INT2NUM(0)
466
+ );
467
+ rb_str_append(body, tmp_buf);
468
+ RB_GC_GUARD(tmp_buf);
469
+ }
470
+ else {
471
+ body = rb_funcall(
472
+ mPolyphony, ID_backend_read, 5, parser->io, Qnil, INT2NUM(maxlen), Qfalse, INT2NUM(0)
473
+ );
474
+ }
475
+ int new_len = RSTRING_LEN(body);
476
+ int read_bytes = new_len - len;
477
+ if (!read_bytes) RAISE_BAD_REQUEST("Incomplete request body");
478
+
479
+ len = new_len;
480
+ left -= read_bytes;
481
+ }
482
+
483
+ RB_GC_GUARD(body);
484
+ return body;
485
+ }
486
+
487
+ VALUE Parser_read_body(VALUE self, VALUE headers) {
488
+ VALUE content_length = rb_hash_aref(headers, STR_content_length);
489
+ if (content_length != Qnil)
490
+ return read_body_with_content_length(self, parse_content_length(content_length));
491
+
492
+ // VALUE transfer_encoding = rb_hash_aref(headers, STR_transfer_encoding);
493
+ // if (chunked_encoding_p(transfer_encoding)) {
494
+ // return read_body_with_chunked_encoding(self);
495
+ // }
496
+
497
+ return Qnil;
498
+ }
499
+
500
+ #define GLOBAL_STR(v, s) v = rb_str_new_literal(s); rb_global_variable(&v)
501
+
502
+ void Init_HTTP1_Parser() {
503
+ VALUE mTipi;
504
+ VALUE cHTTP1Parser;
505
+
506
+ mPolyphony = rb_const_get(rb_cObject, rb_intern("Polyphony"));
507
+
508
+ mTipi = rb_define_module("Tipi");
509
+ cHTTP1Parser = rb_define_class_under(mTipi, "HTTP1Parser", rb_cObject);
510
+ rb_define_alloc_func(cHTTP1Parser, Parser_allocate);
511
+
512
+ cError = rb_define_class_under(cHTTP1Parser, "Error", rb_eRuntimeError);
513
+
514
+ // backend methods
515
+ rb_define_method(cHTTP1Parser, "initialize", Parser_initialize, 1);
516
+ rb_define_method(cHTTP1Parser, "parse_headers", Parser_parse_headers, 0);
517
+ rb_define_method(cHTTP1Parser, "read_body", Parser_read_body, 1);
518
+
519
+ ID_backend_read = rb_intern("backend_read");
520
+ ID_downcase = rb_intern("downcase");
521
+ ID_read = rb_intern("read");
522
+ ID_readpartial = rb_intern("readpartial");
523
+ ID_to_i = rb_intern("to_i");
524
+
525
+ MAX_READ_LENGTH = INT2NUM(4096);
526
+ BUFFER_END = INT2NUM(-1);
527
+
528
+ GLOBAL_STR(STR_pseudo_method, ":method");
529
+ GLOBAL_STR(STR_pseudo_path, ":path");
530
+ GLOBAL_STR(STR_pseudo_protocol, ":protocol");
531
+
532
+ GLOBAL_STR(STR_content_length, "content-length");
533
+ GLOBAL_STR(STR_transfer_encoding, "transfer-encoding");
534
+ }