iodine 0.1.21 → 0.2.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/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
data/ext/iodine/random.h
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
/*
|
2
|
+
(un)copyright: Boaz segev, 2016
|
3
|
+
License: Public Domain except for any non-public-domain algorithms, which are
|
4
|
+
subject to their own licenses.
|
5
|
+
|
6
|
+
Feel free to copy, use and enjoy in accordance with to the license(s).
|
7
|
+
*/
|
8
|
+
#ifndef bscrypt_RANDOM_H
|
9
|
+
#define bscrypt_RANDOM_H
|
10
|
+
#include "bscrypt-common.h"
|
11
|
+
/* *****************************************************************************
|
12
|
+
C++ extern
|
13
|
+
*/
|
14
|
+
#if defined(__cplusplus)
|
15
|
+
extern "C" {
|
16
|
+
#endif
|
17
|
+
|
18
|
+
/* ***************************************************************************
|
19
|
+
Random stuff... (why is this not a system call?)
|
20
|
+
*/
|
21
|
+
|
22
|
+
/** returns 32 random bits. */
|
23
|
+
uint32_t bscrypt_rand32(void);
|
24
|
+
|
25
|
+
/** returns 64 random bits. */
|
26
|
+
uint64_t bscrypt_rand64(void);
|
27
|
+
|
28
|
+
/** returns 128 random bits. */
|
29
|
+
bits128_u bscrypt_rand128(void);
|
30
|
+
|
31
|
+
/** returns 256 random bits. */
|
32
|
+
bits256_u bscrypt_rand256(void);
|
33
|
+
|
34
|
+
/** returns a variable length string of random bytes. */
|
35
|
+
void bscrypt_rand_bytes(void *target, size_t length);
|
36
|
+
|
37
|
+
#if defined(DEBUG) && DEBUG == 1
|
38
|
+
void bscrypt_test_random(void);
|
39
|
+
#endif
|
40
|
+
|
41
|
+
/* *****************************************************************************
|
42
|
+
C++ extern finish
|
43
|
+
*/
|
44
|
+
#if defined(__cplusplus)
|
45
|
+
}
|
46
|
+
#endif
|
47
|
+
|
48
|
+
#endif
|
@@ -0,0 +1,127 @@
|
|
1
|
+
#include "rb-call.h"
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <ruby/thread.h>
|
4
|
+
#include <pthread.h>
|
5
|
+
|
6
|
+
///////////////
|
7
|
+
// this is a simple helper that calls Ruby methods on Ruby objects while within
|
8
|
+
// a non-GVL ruby thread zone.
|
9
|
+
|
10
|
+
// a structure for Ruby API calls
|
11
|
+
struct RubySimpleCall {
|
12
|
+
VALUE obj;
|
13
|
+
VALUE returned;
|
14
|
+
ID method;
|
15
|
+
};
|
16
|
+
struct RubyArgCall {
|
17
|
+
VALUE obj;
|
18
|
+
int argc;
|
19
|
+
VALUE *argv;
|
20
|
+
VALUE returned;
|
21
|
+
ID method;
|
22
|
+
};
|
23
|
+
|
24
|
+
#if __STDC_VERSION__ < 201112L || __STDC_NO_THREADS__
|
25
|
+
#define _Thread_local __thread
|
26
|
+
#endif
|
27
|
+
|
28
|
+
// a thread specific global variable that lets us know if we're in the GVL
|
29
|
+
static _Thread_local char in_gvl = 0;
|
30
|
+
static char check_in_gvl(void) { return in_gvl; }
|
31
|
+
|
32
|
+
////////////////////////////////////////////////////////////////////////////
|
33
|
+
// Calling C functions.
|
34
|
+
static void *call_c(void *(*func)(void *), void *arg) {
|
35
|
+
if (in_gvl) {
|
36
|
+
return func(arg);
|
37
|
+
}
|
38
|
+
void *ret;
|
39
|
+
in_gvl = 1;
|
40
|
+
ret = rb_thread_call_with_gvl(func, arg);
|
41
|
+
in_gvl = 0;
|
42
|
+
return ret;
|
43
|
+
}
|
44
|
+
|
45
|
+
////////////////////////////////////////////////////////////////////////////
|
46
|
+
// Handling exceptions (printing the backtrace doesn't really work well).
|
47
|
+
static void *handle_exception(void *_) {
|
48
|
+
VALUE exc = rb_errinfo();
|
49
|
+
if (exc != Qnil) {
|
50
|
+
VALUE msg = RubyCaller.call(exc, rb_intern("message"));
|
51
|
+
VALUE exc_class = rb_class_name(CLASS_OF(exc));
|
52
|
+
VALUE bt = RubyCaller.call(exc, rb_intern("backtrace"));
|
53
|
+
if (TYPE(bt) == T_ARRAY) {
|
54
|
+
bt = rb_ary_join(bt, rb_str_new_literal("\n"));
|
55
|
+
fprintf(stderr, "Iodine caught an unprotected exception - %.*s: %.*s\n%s",
|
56
|
+
(int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class),
|
57
|
+
(int)RSTRING_LEN(msg), RSTRING_PTR(msg), StringValueCStr(bt));
|
58
|
+
} else {
|
59
|
+
fprintf(stderr, "Iodine caught an unprotected exception - %.*s: %.*s\n"
|
60
|
+
"No backtrace available.\n",
|
61
|
+
(int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class),
|
62
|
+
(int)RSTRING_LEN(msg), RSTRING_PTR(msg));
|
63
|
+
}
|
64
|
+
rb_backtrace();
|
65
|
+
rb_set_errinfo(Qnil);
|
66
|
+
}
|
67
|
+
return (void *)Qnil;
|
68
|
+
}
|
69
|
+
|
70
|
+
////////////////////////////////////////////////////////////////////////////
|
71
|
+
// A simple (and a bit lighter) design for when there's no need for arguments.
|
72
|
+
|
73
|
+
// running the actual method call
|
74
|
+
static VALUE run_ruby_method_unsafe(VALUE _tsk) {
|
75
|
+
struct RubySimpleCall *task = (void *)_tsk;
|
76
|
+
return rb_funcall2(task->obj, task->method, 0, NULL);
|
77
|
+
}
|
78
|
+
|
79
|
+
// GVL gateway
|
80
|
+
static void *run_ruby_method_within_gvl(void *_tsk) {
|
81
|
+
struct RubySimpleCall *task = _tsk;
|
82
|
+
int state = 0;
|
83
|
+
task->returned = rb_protect(run_ruby_method_unsafe, (VALUE)(task), &state);
|
84
|
+
if (state)
|
85
|
+
handle_exception(NULL);
|
86
|
+
return task;
|
87
|
+
}
|
88
|
+
|
89
|
+
// wrapping any API calls for exception management AND GVL entry
|
90
|
+
static VALUE call(VALUE obj, ID method) {
|
91
|
+
struct RubySimpleCall task = {.obj = obj, .method = method};
|
92
|
+
call_c(run_ruby_method_within_gvl, &task);
|
93
|
+
return task.returned;
|
94
|
+
}
|
95
|
+
|
96
|
+
////////////////////////////////////////////////////////////////////////////
|
97
|
+
// A heavier (memory) design for when we're passing arguments around.
|
98
|
+
|
99
|
+
// running the actual method call
|
100
|
+
static VALUE run_argv_method_unsafe(VALUE _tsk) {
|
101
|
+
struct RubyArgCall *task = (void *)_tsk;
|
102
|
+
return rb_funcall2(task->obj, task->method, task->argc, task->argv);
|
103
|
+
}
|
104
|
+
|
105
|
+
// GVL gateway
|
106
|
+
static void *run_argv_method_within_gvl(void *_tsk) {
|
107
|
+
struct RubyArgCall *task = _tsk;
|
108
|
+
int state = 0;
|
109
|
+
task->returned = rb_protect(run_argv_method_unsafe, (VALUE)(task), &state);
|
110
|
+
if (state)
|
111
|
+
handle_exception(NULL);
|
112
|
+
return task;
|
113
|
+
}
|
114
|
+
|
115
|
+
// wrapping any API calls for exception management AND GVL entry
|
116
|
+
static VALUE call_arg(VALUE obj, ID method, int argc, VALUE *argv) {
|
117
|
+
struct RubyArgCall task = {
|
118
|
+
.obj = obj, .method = method, .argc = argc, .argv = argv};
|
119
|
+
call_c(run_argv_method_within_gvl, &task);
|
120
|
+
return task.returned;
|
121
|
+
}
|
122
|
+
|
123
|
+
////////////////////////////////////////////////////////////////////////////
|
124
|
+
// the API interface
|
125
|
+
struct _Ruby_Method_Caller_Class_ RubyCaller = {
|
126
|
+
.call = call, .call2 = call_arg, .call_c = call_c, .in_gvl = check_in_gvl,
|
127
|
+
};
|
@@ -0,0 +1,60 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz segev, 2016
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef RB_CALL_H
|
8
|
+
#define RB_CALL_H
|
9
|
+
#include <ruby.h>
|
10
|
+
|
11
|
+
#define RB_CALL_VERSION "0.2.0"
|
12
|
+
|
13
|
+
/**
|
14
|
+
The Ruby framework manages it's own contextc switching and memory...
|
15
|
+
this means that when we make calls to Ruby (i.e. creating Ruby objects),
|
16
|
+
we risk currupting the Ruby framework. Also, Ruby uses all these signals (for
|
17
|
+
it's context switchings) and long-jumps (i.e. Ruby exceptions) that can drive
|
18
|
+
the C code a little nuts...
|
19
|
+
|
20
|
+
Seperation is reqiured :-)
|
21
|
+
|
22
|
+
This is a simple helper that calls Ruby methods on Ruby objects and C
|
23
|
+
functions that require acess to the Ruby API, while managing the GVL lock
|
24
|
+
status as required.
|
25
|
+
|
26
|
+
To call a Ruby object's method, simply use:
|
27
|
+
|
28
|
+
RubyCaller.call(object, method_id);
|
29
|
+
|
30
|
+
To pass arguments to the ruby object's method, use the `call2` method. i.e.:
|
31
|
+
|
32
|
+
RubyCaller.call2(object, method_id, argc, argv);
|
33
|
+
|
34
|
+
|
35
|
+
This library keeps track of the thread, adjusting the method to be called in
|
36
|
+
case the thread is already within the GVL.
|
37
|
+
|
38
|
+
The library assums that it is within a Ruby thread that is was released of
|
39
|
+
the GVL using `rb_thread_call_without_gvl2` or `rb_thread_call_without_gvl`.
|
40
|
+
*/
|
41
|
+
extern struct _Ruby_Method_Caller_Class_ {
|
42
|
+
/** calls a Object's ruby method, adjusting for the GVL if needed */
|
43
|
+
VALUE (*call)(VALUE object, ID method_id);
|
44
|
+
/**
|
45
|
+
calls a Object's ruby method with arguments, adjusting for the GVL if needed
|
46
|
+
*/
|
47
|
+
VALUE (*call2)(VALUE obj, ID method, int argc, VALUE *argv);
|
48
|
+
/**
|
49
|
+
calls a C method that requires the GVL for access to the Ruby API, managing the
|
50
|
+
GVL state as required.
|
51
|
+
*/
|
52
|
+
void *(*call_c)(void *(*func)(void *), void *arg);
|
53
|
+
/**
|
54
|
+
returns the thread's GVL status (1 if inside a GVL and 0 if free from the
|
55
|
+
GVL).
|
56
|
+
*/
|
57
|
+
char (*in_gvl)(void);
|
58
|
+
} RubyCaller;
|
59
|
+
|
60
|
+
#endif /* RB_CALL_H */
|
@@ -0,0 +1,79 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz segev, 2016
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef RB_ASYNC_EXT_H
|
8
|
+
#define RB_ASYNC_EXT_H
|
9
|
+
#include <ruby.h>
|
10
|
+
#include <ruby/thread.h>
|
11
|
+
#include "rb-registry.h"
|
12
|
+
|
13
|
+
/******************************************************************************
|
14
|
+
Portability - used to help port this to different frameworks (i.e. Ruby).
|
15
|
+
*/
|
16
|
+
|
17
|
+
#define THREAD_TYPE VALUE
|
18
|
+
|
19
|
+
/* Don't use sentinals with Ruby */
|
20
|
+
#ifndef ASYNC_USE_SENTINEL
|
21
|
+
#define ASYNC_USE_SENTINEL 0
|
22
|
+
#endif
|
23
|
+
|
24
|
+
/* The unused directive */
|
25
|
+
#ifndef __unused
|
26
|
+
#define __unused __attribute__((unused))
|
27
|
+
#endif
|
28
|
+
|
29
|
+
/* used here but declared elsewhere */
|
30
|
+
void async_signal();
|
31
|
+
|
32
|
+
/* used here but declared elsewhere */
|
33
|
+
void call_async_signal(void *_) { async_signal(); }
|
34
|
+
|
35
|
+
/* protect the call to join from any exceptions */
|
36
|
+
static void *_inner_join_with_rbthread(void *rbt) {
|
37
|
+
return (void *)rb_funcall((VALUE)rbt, rb_intern("join"), 0);
|
38
|
+
}
|
39
|
+
|
40
|
+
/* join a ruby thread */
|
41
|
+
__unused static void *join_thread(THREAD_TYPE thr) {
|
42
|
+
void *ret = rb_thread_call_with_gvl(_inner_join_with_rbthread, (void *)thr);
|
43
|
+
Registry.remove(thr);
|
44
|
+
return ret;
|
45
|
+
}
|
46
|
+
/* used to create Ruby threads and pass them the information they need */
|
47
|
+
struct CreateThreadArgs {
|
48
|
+
void *(*thread_func)(void *);
|
49
|
+
void *arg;
|
50
|
+
};
|
51
|
+
|
52
|
+
/* the thread's GVL release */
|
53
|
+
static VALUE thread_loop(void *_args) {
|
54
|
+
struct CreateThreadArgs *args = _args;
|
55
|
+
void *(*thread_func)(void *) = args->thread_func;
|
56
|
+
void *arg = args->arg;
|
57
|
+
free(_args);
|
58
|
+
rb_thread_call_without_gvl2(thread_func, arg,
|
59
|
+
(void (*)(void *))call_async_signal, arg);
|
60
|
+
return Qnil;
|
61
|
+
}
|
62
|
+
|
63
|
+
/* Within the GVL, creates a Ruby thread using an API call */
|
64
|
+
static void *create_ruby_thread_gvl(void *_args) {
|
65
|
+
return (void *)Registry.add(rb_thread_create(thread_loop, _args));
|
66
|
+
}
|
67
|
+
|
68
|
+
/* create a ruby thread */
|
69
|
+
__unused static int create_thread(THREAD_TYPE *thr,
|
70
|
+
void *(*thread_func)(void *), void *arg) {
|
71
|
+
struct CreateThreadArgs *data = malloc(sizeof(*data));
|
72
|
+
if (!data)
|
73
|
+
return -1;
|
74
|
+
*data = (struct CreateThreadArgs){.thread_func = thread_func, .arg = arg};
|
75
|
+
*thr = (VALUE)rb_thread_call_with_gvl(create_ruby_thread_gvl, data);
|
76
|
+
return *thr == Qnil;
|
77
|
+
}
|
78
|
+
|
79
|
+
#endif
|
@@ -0,0 +1,389 @@
|
|
1
|
+
#include "rb-rack-io.h"
|
2
|
+
#include "rb-call.h"
|
3
|
+
#include "iodine_core.h"
|
4
|
+
#include <ruby/encoding.h>
|
5
|
+
#include <ruby/io.h>
|
6
|
+
#include <ruby/io.h>
|
7
|
+
|
8
|
+
/* RackIO manages a minimal interface to act as an IO wrapper according to
|
9
|
+
these Rack specifications:
|
10
|
+
|
11
|
+
The input stream is an IO-like object which contains the raw HTTP POST data.
|
12
|
+
When applicable, its external encoding must be “ASCII-8BIT” and it must be
|
13
|
+
opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond
|
14
|
+
to gets, each, read and rewind.
|
15
|
+
|
16
|
+
gets must be called without arguments and return a string, or nil on EOF.
|
17
|
+
|
18
|
+
read behaves like IO#read. Its signature is read([length, [buffer]]). If given,
|
19
|
+
length must be a non-negative Integer (>= 0) or nil, and buffer must be a String
|
20
|
+
and may not be nil. If length is given and not nil, then this method reads at
|
21
|
+
most length bytes from the input stream. If length is not given or nil, then
|
22
|
+
this method reads all data until EOF. When EOF is reached, this method returns
|
23
|
+
nil if length is given and not nil, or “” if length is not given or is nil. If
|
24
|
+
buffer is given, then the read data will be placed into buffer instead of a
|
25
|
+
newly created String object.
|
26
|
+
|
27
|
+
each must be called without arguments and only yield Strings.
|
28
|
+
|
29
|
+
rewind must be called without arguments. It rewinds the input stream back to the
|
30
|
+
beginning. It must not raise Errno::ESPIPE: that is, it may not be a pipe or a
|
31
|
+
socket. Therefore, handler developers must buffer the input data into some
|
32
|
+
rewindable object if the underlying input stream is not rewindable.
|
33
|
+
|
34
|
+
close must never be called on the input stream.
|
35
|
+
|
36
|
+
*/
|
37
|
+
|
38
|
+
/* *****************************************************************************
|
39
|
+
Core data / helpers
|
40
|
+
*/
|
41
|
+
|
42
|
+
static VALUE rRackStrIO;
|
43
|
+
static VALUE rRackFileIO;
|
44
|
+
|
45
|
+
static ID pos_id;
|
46
|
+
static ID end_id;
|
47
|
+
static ID env_id;
|
48
|
+
static ID io_id;
|
49
|
+
|
50
|
+
static VALUE TCPSOCKET_CLASS;
|
51
|
+
static ID for_fd_id;
|
52
|
+
|
53
|
+
#define set_uuid(object, request) \
|
54
|
+
rb_ivar_set((object), fd_var_id, ULONG2NUM((request)->metadata.fd))
|
55
|
+
|
56
|
+
inline static intptr_t get_uuid(VALUE obj) {
|
57
|
+
VALUE i = rb_ivar_get(obj, fd_var_id);
|
58
|
+
return (intptr_t)FIX2ULONG(i);
|
59
|
+
}
|
60
|
+
|
61
|
+
#define set_pos(object, pos) rb_ivar_set((object), pos_id, ULONG2NUM(pos))
|
62
|
+
|
63
|
+
inline static size_t get_pos(VALUE obj) {
|
64
|
+
VALUE i = rb_ivar_get(obj, pos_id);
|
65
|
+
return (size_t)FIX2ULONG(i);
|
66
|
+
}
|
67
|
+
|
68
|
+
inline static size_t get_end(VALUE obj) {
|
69
|
+
VALUE i = rb_ivar_get(obj, end_id);
|
70
|
+
return (size_t)FIX2ULONG(i);
|
71
|
+
}
|
72
|
+
|
73
|
+
/* *****************************************************************************
|
74
|
+
StrIO API
|
75
|
+
*/
|
76
|
+
|
77
|
+
// a macro helper to get the server pointer embeded in an object
|
78
|
+
inline static char *get_str(VALUE obj) {
|
79
|
+
VALUE i = rb_ivar_get(obj, io_id);
|
80
|
+
return (char *)FIX2ULONG(i);
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
Gets returns a line. this is okay for small lines,
|
85
|
+
but shouldn't really be used.
|
86
|
+
|
87
|
+
Limited to ~ 1Mb of a line length.
|
88
|
+
*/
|
89
|
+
static VALUE strio_gets(VALUE self) {
|
90
|
+
char *str = get_str(self);
|
91
|
+
size_t pos = get_pos(self);
|
92
|
+
size_t end = get_end(self);
|
93
|
+
if (str == NULL || pos == end)
|
94
|
+
return Qnil;
|
95
|
+
size_t pos_e = pos;
|
96
|
+
|
97
|
+
while ((pos_e < end) && str[pos_e] != '\n')
|
98
|
+
pos_e++;
|
99
|
+
set_pos(self, pos_e + 1);
|
100
|
+
return rb_enc_str_new(str + pos, pos_e - pos, BinaryEncoding);
|
101
|
+
}
|
102
|
+
|
103
|
+
// Reads data from the IO, according to the Rack specifications for `#read`.
|
104
|
+
static VALUE strio_read(int argc, VALUE *argv, VALUE self) {
|
105
|
+
char *str = get_str(self);
|
106
|
+
size_t pos = get_pos(self);
|
107
|
+
size_t end = get_end(self);
|
108
|
+
VALUE buffer = Qnil;
|
109
|
+
char ret_nil = 0;
|
110
|
+
ssize_t len = 0;
|
111
|
+
// get the buffer object if given
|
112
|
+
if (argc == 2) {
|
113
|
+
Check_Type(argv[1], T_STRING);
|
114
|
+
buffer = argv[1];
|
115
|
+
}
|
116
|
+
// get the length object, if given
|
117
|
+
if (argc > 0 && argv[0] != Qnil) {
|
118
|
+
Check_Type(argv[0], T_FIXNUM);
|
119
|
+
len = FIX2LONG(argv[0]);
|
120
|
+
if (len < 0)
|
121
|
+
rb_raise(rb_eRangeError, "length should be bigger then 0.");
|
122
|
+
ret_nil = 1;
|
123
|
+
}
|
124
|
+
// return if we're at the EOF.
|
125
|
+
if (str == NULL)
|
126
|
+
goto no_data;
|
127
|
+
// calculate length if it wasn't specified.
|
128
|
+
if (len == 0) {
|
129
|
+
// make sure we're not reading more then we have (string buffer)
|
130
|
+
len = end - pos;
|
131
|
+
// set position for future reads
|
132
|
+
set_pos(self, end);
|
133
|
+
if (len == 0)
|
134
|
+
goto no_data;
|
135
|
+
} else {
|
136
|
+
// set position for future reads
|
137
|
+
set_pos(self, pos + len);
|
138
|
+
}
|
139
|
+
if (len + pos > end)
|
140
|
+
len = end - pos;
|
141
|
+
// create the buffer if we don't have one.
|
142
|
+
if (buffer == Qnil) {
|
143
|
+
buffer = rb_str_buf_new(len);
|
144
|
+
// make sure the buffer is binary encoded.
|
145
|
+
rb_enc_associate(buffer, BinaryEncoding);
|
146
|
+
} else {
|
147
|
+
// make sure the buffer is binary encoded.
|
148
|
+
rb_enc_associate(buffer, BinaryEncoding);
|
149
|
+
if (rb_str_capacity(buffer) < len)
|
150
|
+
rb_str_resize(buffer, len);
|
151
|
+
}
|
152
|
+
// read the data.
|
153
|
+
memcpy(RSTRING_PTR(buffer), str + pos, len);
|
154
|
+
rb_str_set_len(buffer, len);
|
155
|
+
return buffer;
|
156
|
+
no_data:
|
157
|
+
if (ret_nil)
|
158
|
+
return Qnil;
|
159
|
+
else
|
160
|
+
return rb_str_buf_new(0);
|
161
|
+
}
|
162
|
+
|
163
|
+
// Does nothing - this is controlled by the server.
|
164
|
+
static VALUE strio_close(VALUE self) { return Qnil; }
|
165
|
+
|
166
|
+
// Rewinds the IO, so that it is read from the begining.
|
167
|
+
static VALUE rio_rewind(VALUE self) {
|
168
|
+
set_pos(self, 0);
|
169
|
+
return self;
|
170
|
+
}
|
171
|
+
|
172
|
+
// Passes each line of the input to the block. This should be avoided.
|
173
|
+
static VALUE strio_each(VALUE self) {
|
174
|
+
rb_need_block();
|
175
|
+
rio_rewind(self);
|
176
|
+
VALUE str = Qnil;
|
177
|
+
while ((str = strio_gets(self)) != Qnil) {
|
178
|
+
rb_yield(str);
|
179
|
+
}
|
180
|
+
return self;
|
181
|
+
}
|
182
|
+
|
183
|
+
/* *****************************************************************************
|
184
|
+
TempFileIO API
|
185
|
+
*/
|
186
|
+
|
187
|
+
// a macro helper to get the server pointer embeded in an object
|
188
|
+
inline static int get_tmpfile(VALUE obj) {
|
189
|
+
VALUE i = rb_ivar_get(obj, io_id);
|
190
|
+
return (int)FIX2INT(i);
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
Gets returns a line. this is okay for small lines,
|
195
|
+
but shouldn't really be used.
|
196
|
+
|
197
|
+
Limited to ~ 1Mb of a line length.
|
198
|
+
*/
|
199
|
+
static VALUE tfio_gets(VALUE self) {
|
200
|
+
int fd = get_tmpfile(self);
|
201
|
+
size_t pos = get_pos(self);
|
202
|
+
size_t end = get_end(self);
|
203
|
+
if (pos == end)
|
204
|
+
return Qnil;
|
205
|
+
size_t pos_e = pos;
|
206
|
+
char c;
|
207
|
+
int ret;
|
208
|
+
VALUE buffer;
|
209
|
+
|
210
|
+
do {
|
211
|
+
ret = pread(fd, &c, 1, pos_e);
|
212
|
+
} while (ret > 0 && c != '\n' && (++pos_e < end));
|
213
|
+
set_pos(self, pos_e + 1);
|
214
|
+
if (pos > pos_e) {
|
215
|
+
buffer = rb_str_buf_new(pos_e - pos);
|
216
|
+
// make sure the buffer is binary encoded.
|
217
|
+
rb_enc_associate(buffer, BinaryEncoding);
|
218
|
+
if (pread(fd, RSTRING_PTR(buffer), pos_e - pos, pos) < 0)
|
219
|
+
return Qnil;
|
220
|
+
rb_str_set_len(buffer, pos_e - pos);
|
221
|
+
return buffer;
|
222
|
+
}
|
223
|
+
return Qnil;
|
224
|
+
}
|
225
|
+
|
226
|
+
// Reads data from the IO, according to the Rack specifications for `#read`.
|
227
|
+
static VALUE tfio_read(int argc, VALUE *argv, VALUE self) {
|
228
|
+
int fd = get_tmpfile(self);
|
229
|
+
size_t pos = get_pos(self);
|
230
|
+
size_t end = get_end(self);
|
231
|
+
VALUE buffer = Qnil;
|
232
|
+
char ret_nil = 0;
|
233
|
+
ssize_t len = 0;
|
234
|
+
// get the buffer object if given
|
235
|
+
if (argc == 2) {
|
236
|
+
Check_Type(argv[1], T_STRING);
|
237
|
+
buffer = argv[1];
|
238
|
+
}
|
239
|
+
// get the length object, if given
|
240
|
+
if (argc > 0 && argv[0] != Qnil) {
|
241
|
+
Check_Type(argv[0], T_FIXNUM);
|
242
|
+
len = FIX2LONG(argv[0]);
|
243
|
+
if (len < 0)
|
244
|
+
rb_raise(rb_eRangeError, "length should be bigger then 0.");
|
245
|
+
ret_nil = 1;
|
246
|
+
}
|
247
|
+
// return if we're at the EOF.
|
248
|
+
if (pos == end)
|
249
|
+
goto no_data;
|
250
|
+
// calculate length if it wasn't specified.
|
251
|
+
if (len == 0) {
|
252
|
+
// make sure we're not reading more then we have
|
253
|
+
len = end - pos;
|
254
|
+
// set position for future reads
|
255
|
+
set_pos(self, end);
|
256
|
+
if (len == 0)
|
257
|
+
goto no_data;
|
258
|
+
} else {
|
259
|
+
// set position for future reads
|
260
|
+
set_pos(self, pos + len);
|
261
|
+
}
|
262
|
+
// limit read to what we have
|
263
|
+
if (len + pos > end)
|
264
|
+
len = end - pos;
|
265
|
+
// create the buffer if we don't have one.
|
266
|
+
if (buffer == Qnil) {
|
267
|
+
buffer = rb_str_buf_new(len);
|
268
|
+
// make sure the buffer is binary encoded.
|
269
|
+
rb_enc_associate(buffer, BinaryEncoding);
|
270
|
+
} else {
|
271
|
+
// make sure the buffer is binary encoded.
|
272
|
+
rb_enc_associate(buffer, BinaryEncoding);
|
273
|
+
if (rb_str_capacity(buffer) < len)
|
274
|
+
rb_str_resize(buffer, len);
|
275
|
+
}
|
276
|
+
// read the data.
|
277
|
+
if (pread(fd, RSTRING_PTR(buffer), len, pos) <= 0)
|
278
|
+
goto no_data;
|
279
|
+
rb_str_set_len(buffer, len);
|
280
|
+
return buffer;
|
281
|
+
no_data:
|
282
|
+
if (ret_nil)
|
283
|
+
return Qnil;
|
284
|
+
else
|
285
|
+
return rb_str_buf_new(0);
|
286
|
+
}
|
287
|
+
|
288
|
+
// Does nothing - this is controlled by the server.
|
289
|
+
static VALUE tfio_close(VALUE self) { return Qnil; }
|
290
|
+
|
291
|
+
// Passes each line of the input to the block. This should be avoided.
|
292
|
+
static VALUE tfio_each(VALUE self) {
|
293
|
+
rb_need_block();
|
294
|
+
rio_rewind(self);
|
295
|
+
VALUE str = Qnil;
|
296
|
+
while ((str = tfio_gets(self)) != Qnil) {
|
297
|
+
rb_yield(str);
|
298
|
+
}
|
299
|
+
return self;
|
300
|
+
}
|
301
|
+
|
302
|
+
/* *****************************************************************************
|
303
|
+
Hijacking
|
304
|
+
*/
|
305
|
+
|
306
|
+
// defined by iodine_http
|
307
|
+
extern VALUE R_HIJACK; // for Rack: rack.hijack
|
308
|
+
extern VALUE R_HIJACK_CB; // for Rack: rack.hijack
|
309
|
+
extern VALUE R_HIJACK_IO; // for Rack: rack.hijack_io
|
310
|
+
|
311
|
+
static VALUE rio_get_io(int argc, VALUE *argv, VALUE self) {
|
312
|
+
if (TCPSOCKET_CLASS == Qnil)
|
313
|
+
return Qfalse;
|
314
|
+
intptr_t fduuid = get_uuid(self);
|
315
|
+
// hijack the IO object
|
316
|
+
VALUE fd = INT2FIX(sock_uuid2fd(fduuid));
|
317
|
+
VALUE env = rb_ivar_get(self, env_id);
|
318
|
+
// make sure we're not repeating ourselves
|
319
|
+
VALUE new_io = rb_hash_aref(env, R_HIJACK_IO);
|
320
|
+
if (new_io != Qnil)
|
321
|
+
return new_io;
|
322
|
+
// VALUE new_io = how the fuck do we create a new IO from the fd?
|
323
|
+
new_io = RubyCaller.call2(TCPSOCKET_CLASS, for_fd_id, 1,
|
324
|
+
&fd); // TCPSocket.for_fd(fd) ... cool...
|
325
|
+
rb_hash_aset(env, R_HIJACK_IO, new_io);
|
326
|
+
if (argc)
|
327
|
+
rb_hash_aset(env, R_HIJACK_CB, *argv);
|
328
|
+
return new_io;
|
329
|
+
}
|
330
|
+
|
331
|
+
/* *****************************************************************************
|
332
|
+
C land API
|
333
|
+
*/
|
334
|
+
|
335
|
+
// new object
|
336
|
+
static VALUE new_rack_io(http_request_s *request, VALUE env) {
|
337
|
+
VALUE rack_io;
|
338
|
+
if (request->body_file > 0) {
|
339
|
+
rack_io = rb_funcall2(rRackFileIO, new_func_id, 0, NULL);
|
340
|
+
rb_ivar_set(rack_io, io_id, ULONG2NUM(request->body_file));
|
341
|
+
lseek(request->body_file, 0, SEEK_SET);
|
342
|
+
} else {
|
343
|
+
rack_io = rb_funcall2(rRackStrIO, new_func_id, 0, NULL);
|
344
|
+
rb_ivar_set(rack_io, io_id, ULONG2NUM(((intptr_t)request->body_str)));
|
345
|
+
// fprintf(stderr, "rack body IO (%lu, %p):%.*s\n", request->content_length,
|
346
|
+
// request->body_str, (int)request->content_length,
|
347
|
+
// request->body_str);
|
348
|
+
}
|
349
|
+
set_uuid(rack_io, request);
|
350
|
+
set_pos(rack_io, 0);
|
351
|
+
rb_ivar_set(rack_io, end_id, ULONG2NUM(request->content_length));
|
352
|
+
rb_ivar_set(rack_io, env_id, env);
|
353
|
+
|
354
|
+
return rack_io;
|
355
|
+
}
|
356
|
+
|
357
|
+
// initialize library
|
358
|
+
static void init_rack_io(void) {
|
359
|
+
rRackStrIO = rb_define_class_under(IodineBase, "RackStrIO", rb_cObject);
|
360
|
+
rRackFileIO = rb_define_class_under(IodineBase, "RackTmpFileIO", rb_cObject);
|
361
|
+
|
362
|
+
pos_id = rb_intern("pos");
|
363
|
+
end_id = rb_intern("io_end");
|
364
|
+
io_id = rb_intern("rack_io");
|
365
|
+
env_id = rb_intern("env");
|
366
|
+
for_fd_id = rb_intern("for_fd");
|
367
|
+
|
368
|
+
TCPSOCKET_CLASS = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
369
|
+
// IO methods
|
370
|
+
rb_define_method(rRackStrIO, "rewind", rio_rewind, 0);
|
371
|
+
rb_define_method(rRackStrIO, "gets", strio_gets, 0);
|
372
|
+
rb_define_method(rRackStrIO, "read", strio_read, -1);
|
373
|
+
rb_define_method(rRackStrIO, "close", strio_close, 0);
|
374
|
+
rb_define_method(rRackStrIO, "each", strio_each, 0);
|
375
|
+
rb_define_method(rRackStrIO, "_hijack", rio_get_io, -1);
|
376
|
+
|
377
|
+
rb_define_method(rRackFileIO, "rewind", rio_rewind, 0);
|
378
|
+
rb_define_method(rRackFileIO, "gets", tfio_gets, 0);
|
379
|
+
rb_define_method(rRackFileIO, "read", tfio_read, -1);
|
380
|
+
rb_define_method(rRackFileIO, "close", tfio_close, 0);
|
381
|
+
rb_define_method(rRackFileIO, "each", tfio_each, 0);
|
382
|
+
rb_define_method(rRackFileIO, "_hijack", rio_get_io, -1);
|
383
|
+
}
|
384
|
+
|
385
|
+
////////////////////////////////////////////////////////////////////////////
|
386
|
+
// the API interface
|
387
|
+
struct _RackIO_ RackIO = {
|
388
|
+
.new = new_rack_io, .init = init_rack_io,
|
389
|
+
};
|