iodine 0.7.59 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.clang-format +15 -0
- data/.rspec +1 -0
- data/.yardopts +1 -3
- data/CHANGELOG.md +132 -12
- data/Gemfile +5 -7
- data/LICENSE.txt +1 -1
- data/README.md +147 -671
- data/Rakefile +9 -16
- data/examples/chatroom.nru +9 -0
- data/examples/client +74 -0
- data/examples/config-hello.nru +9 -0
- data/examples/config-hello.ru +11 -0
- data/examples/config-hijack.ru +13 -0
- data/examples/config-shootout.ru +43 -0
- data/examples/config.nru +120 -0
- data/examples/config.rb +37 -0
- data/examples/config.ru +42 -53
- data/examples/fake +24 -0
- data/examples/hello_neorack +137 -0
- data/examples/routes.nru +29 -0
- data/examples/routes.ru +42 -0
- data/exe/iodine +218 -258
- data/ext/iodine/extconf.rb +27 -100
- data/ext/iodine/fio-stl.h +128336 -0
- data/ext/iodine/fio-stl.md +26396 -0
- data/ext/iodine/iodine.c +213 -1393
- data/ext/iodine/iodine.h +250 -55
- data/ext/iodine/iodine_arg_helper.h +329 -0
- data/ext/iodine/iodine_caller.h +234 -25
- data/ext/iodine/iodine_cli.h +434 -0
- data/ext/iodine/iodine_compression.h +312 -0
- data/ext/iodine/iodine_connection.h +3484 -34
- data/ext/iodine/iodine_core.h +363 -0
- data/ext/iodine/iodine_crypto.h +1441 -0
- data/ext/iodine/iodine_defer.h +225 -4
- data/ext/iodine/iodine_json.h +738 -4
- data/ext/iodine/iodine_listener.h +263 -0
- data/ext/iodine/iodine_log.h +297 -0
- data/ext/iodine/iodine_minimap.h +543 -0
- data/ext/iodine/iodine_mustache.h +461 -4
- data/ext/iodine/iodine_pubsub_eng.h +729 -0
- data/ext/iodine/iodine_pubsub_history.h +547 -0
- data/ext/iodine/iodine_pubsub_msg.h +228 -0
- data/ext/iodine/iodine_redis.h +322 -0
- data/ext/iodine/iodine_store.h +418 -16
- data/ext/iodine/iodine_threads.h +279 -0
- data/ext/iodine/iodine_tls.h +515 -6
- data/ext/iodine/iodine_utils.h +923 -0
- data/iodine.gemspec +37 -42
- data/lib/iodine/benchmark.rb +1690 -0
- data/lib/iodine/documentation.rb +3085 -0
- data/lib/iodine/version.rb +17 -1
- data/lib/iodine.rb +54 -255
- data/logo.svg +36 -0
- data/mustache.md +55 -0
- data/sig/iodine.rbs +4 -0
- metadata +58 -184
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- data/.github/workflows/ruby.yml +0 -42
- data/.gitignore +0 -20
- data/LIMITS.md +0 -41
- data/SPEC-PubSub-Draft.md +0 -159
- data/SPEC-WebSocket-Draft.md +0 -239
- data/bin/console +0 -22
- data/bin/info.md +0 -353
- data/bin/mustache_bench.rb +0 -100
- data/bin/poc/Gemfile.lock +0 -23
- data/bin/poc/README.md +0 -37
- data/bin/poc/config.ru +0 -66
- data/bin/poc/gemfile +0 -1
- data/bin/poc/www/index.html +0 -57
- data/examples/async_task.ru +0 -92
- data/examples/bates/README.md +0 -3
- data/examples/bates/config.ru +0 -342
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +0 -600
- data/examples/echo.ru +0 -59
- data/examples/etag.ru +0 -16
- data/examples/hello.ru +0 -29
- data/examples/pubsub_engine.ru +0 -81
- data/examples/rack3.ru +0 -12
- data/examples/redis.ru +0 -70
- data/examples/shootout.ru +0 -73
- data/examples/sub-protocols.ru +0 -90
- data/examples/tcp_client.rb +0 -66
- data/examples/x-sendfile.ru +0 -14
- data/ext/iodine/fio.c +0 -11995
- data/ext/iodine/fio.h +0 -6371
- data/ext/iodine/fio_cli.c +0 -431
- data/ext/iodine/fio_cli.h +0 -189
- data/ext/iodine/fio_json_parser.h +0 -688
- data/ext/iodine/fio_siphash.c +0 -157
- data/ext/iodine/fio_siphash.h +0 -37
- data/ext/iodine/fio_tls.h +0 -129
- data/ext/iodine/fio_tls_missing.c +0 -649
- data/ext/iodine/fio_tls_openssl.c +0 -1106
- data/ext/iodine/fio_tmpfile.h +0 -50
- data/ext/iodine/fiobj.h +0 -44
- data/ext/iodine/fiobj4fio.h +0 -21
- data/ext/iodine/fiobj_ary.c +0 -333
- data/ext/iodine/fiobj_ary.h +0 -139
- data/ext/iodine/fiobj_data.c +0 -1187
- data/ext/iodine/fiobj_data.h +0 -167
- data/ext/iodine/fiobj_hash.c +0 -409
- data/ext/iodine/fiobj_hash.h +0 -176
- data/ext/iodine/fiobj_json.c +0 -622
- data/ext/iodine/fiobj_json.h +0 -68
- data/ext/iodine/fiobj_mem.h +0 -71
- data/ext/iodine/fiobj_mustache.c +0 -317
- data/ext/iodine/fiobj_mustache.h +0 -62
- data/ext/iodine/fiobj_numbers.c +0 -344
- data/ext/iodine/fiobj_numbers.h +0 -127
- data/ext/iodine/fiobj_str.c +0 -433
- data/ext/iodine/fiobj_str.h +0 -172
- data/ext/iodine/fiobject.c +0 -620
- data/ext/iodine/fiobject.h +0 -655
- data/ext/iodine/hpack.h +0 -1923
- data/ext/iodine/http.c +0 -2743
- data/ext/iodine/http.h +0 -1002
- data/ext/iodine/http1.c +0 -910
- data/ext/iodine/http1.h +0 -29
- data/ext/iodine/http1_parser.h +0 -878
- data/ext/iodine/http_internal.c +0 -1279
- data/ext/iodine/http_internal.h +0 -237
- data/ext/iodine/http_mime_parser.h +0 -350
- data/ext/iodine/iodine_caller.c +0 -230
- data/ext/iodine/iodine_connection.c +0 -941
- data/ext/iodine/iodine_defer.c +0 -420
- data/ext/iodine/iodine_fiobj2rb.h +0 -120
- data/ext/iodine/iodine_helpers.c +0 -282
- data/ext/iodine/iodine_helpers.h +0 -12
- data/ext/iodine/iodine_http.c +0 -1181
- data/ext/iodine/iodine_http.h +0 -23
- data/ext/iodine/iodine_json.c +0 -302
- data/ext/iodine/iodine_mustache.c +0 -567
- data/ext/iodine/iodine_pubsub.c +0 -580
- data/ext/iodine/iodine_pubsub.h +0 -26
- data/ext/iodine/iodine_rack_io.c +0 -273
- data/ext/iodine/iodine_rack_io.h +0 -20
- data/ext/iodine/iodine_store.c +0 -146
- data/ext/iodine/iodine_tcp.c +0 -346
- data/ext/iodine/iodine_tcp.h +0 -13
- data/ext/iodine/iodine_tls.c +0 -261
- data/ext/iodine/mustache_parser.h +0 -1546
- data/ext/iodine/redis_engine.c +0 -957
- data/ext/iodine/redis_engine.h +0 -79
- data/ext/iodine/resp_parser.h +0 -317
- data/ext/iodine/websocket_parser.h +0 -506
- data/ext/iodine/websockets.c +0 -743
- data/ext/iodine/websockets.h +0 -185
- data/lib/iodine/connection.rb +0 -61
- data/lib/iodine/json.rb +0 -42
- data/lib/iodine/mustache.rb +0 -113
- data/lib/iodine/pubsub.rb +0 -55
- data/lib/iodine/rack_utils.rb +0 -43
- data/lib/iodine/tls.rb +0 -16
- data/lib/rack/handler/iodine.rb +0 -33
- data/logo.png +0 -0
|
@@ -1,55 +1,3505 @@
|
|
|
1
|
-
#ifndef
|
|
2
|
-
|
|
1
|
+
#ifndef H___IODINE_CONNECTION___H
|
|
2
|
+
/** ****************************************************************************
|
|
3
|
+
Iodine::Connection - HTTP/WebSocket/SSE/TCP Connection Management
|
|
3
4
|
|
|
5
|
+
This module is the heart of Iodine's connection handling. It manages:
|
|
6
|
+
- HTTP request/response cycles (Rack-compatible)
|
|
7
|
+
- WebSocket connections (full-duplex messaging)
|
|
8
|
+
- Server-Sent Events (SSE) connections
|
|
9
|
+
- Raw TCP/IP connections
|
|
10
|
+
- Pub/Sub subscriptions per connection
|
|
11
|
+
|
|
12
|
+
## Ruby API Overview
|
|
13
|
+
|
|
14
|
+
### Connection State Methods
|
|
15
|
+
|
|
16
|
+
- `open?` - Returns true if connection is open
|
|
17
|
+
- `valid?` - Returns true if data can still be sent
|
|
18
|
+
- `pending` - Returns bytes waiting to be sent
|
|
19
|
+
- `close` - Schedules connection to close
|
|
20
|
+
- `headers_sent?` - Returns true if HTTP headers were sent
|
|
21
|
+
|
|
22
|
+
### HTTP Request Properties (getters/setters)
|
|
23
|
+
|
|
24
|
+
- `method` / `method=` - HTTP method (GET, POST, etc.)
|
|
25
|
+
- `path` / `path=` - Request path
|
|
26
|
+
- `opath` / `opath=` - Original path (before routing)
|
|
27
|
+
- `query` / `query=` - Query string
|
|
28
|
+
- `version` / `version=` - HTTP version
|
|
29
|
+
- `status` / `status=` - Response status code
|
|
30
|
+
- `env` / `env=` - Rack environment hash
|
|
31
|
+
- `handler` / `handler=` - Connection handler object
|
|
32
|
+
|
|
33
|
+
### HTTP Response Methods
|
|
34
|
+
|
|
35
|
+
- `write_header(name, value)` - Sets a response header
|
|
36
|
+
- `write(data)` - Streams data to client
|
|
37
|
+
- `write_sse(id:, event:, data:)` - Writes SSE event
|
|
38
|
+
- `finish([data])` - Completes the response
|
|
39
|
+
|
|
40
|
+
### Header/Cookie Access
|
|
41
|
+
|
|
42
|
+
- `headers` - Copies headers to internal map for iteration
|
|
43
|
+
- `[key]` / `[key]=` - Hash-like header access
|
|
44
|
+
- `each { |k,v| }` - Iterate over headers
|
|
45
|
+
- `cookie(name)` - Get cookie value
|
|
46
|
+
- `set_cookie(name:, value:, ...)` - Set response cookie
|
|
47
|
+
- `each_cookie { |name, value| }` - Iterate cookies
|
|
48
|
+
|
|
49
|
+
### Body Access (for rack.input compatibility)
|
|
50
|
+
|
|
51
|
+
- `length` - Body length in bytes
|
|
52
|
+
- `read([maxlen], [out_string])` - Read body data
|
|
53
|
+
- `gets([limit])` - Read line from body
|
|
54
|
+
- `seek(pos)` / `rewind` - Seek in body
|
|
55
|
+
- `close_body` - Release body resources (frees buffer / temporary file)
|
|
56
|
+
|
|
57
|
+
### Network Information
|
|
58
|
+
|
|
59
|
+
- `peer_addr` - Raw peer IP address
|
|
60
|
+
- `from` - Published peer address (may differ with proxies)
|
|
61
|
+
|
|
62
|
+
### Connection Type Detection
|
|
63
|
+
|
|
64
|
+
- `websocket?` - Returns true for WebSocket connections
|
|
65
|
+
- `sse?` - Returns true for SSE connections
|
|
66
|
+
|
|
67
|
+
### Pub/Sub Methods (instance and class methods)
|
|
68
|
+
|
|
69
|
+
- `subscribe(channel:, filter:, callback:)` - Subscribe to messages
|
|
70
|
+
- `unsubscribe(channel:, filter:)` - Unsubscribe
|
|
71
|
+
- `publish(channel:, message:, filter:, engine:)` - Publish message
|
|
72
|
+
- `pubsub?` - Returns true (deprecated, use Server.extensions[:pubsub])
|
|
73
|
+
|
|
74
|
+
## Handler Callbacks
|
|
75
|
+
|
|
76
|
+
Handlers should implement these methods (defaults provided if missing):
|
|
77
|
+
|
|
78
|
+
- `on_http(client)` - HTTP request received
|
|
79
|
+
- `on_open(client)` - WebSocket/SSE connection opened
|
|
80
|
+
- `on_message(client, message)` - WebSocket message received
|
|
81
|
+
- `on_eventsource(client, message)` - SSE event received
|
|
82
|
+
- `on_eventsource_reconnect(client, id)` - SSE reconnect with last ID
|
|
83
|
+
- `on_drained(client)` - Output buffer empty
|
|
84
|
+
- `on_shutdown(client)` - Server shutting down
|
|
85
|
+
- `on_close(client)` - Connection closed
|
|
86
|
+
- `on_finish(client)` - Request/response cycle complete
|
|
87
|
+
- `on_authenticate_websocket(client)` - Authenticate WS upgrade
|
|
88
|
+
- `on_authenticate_sse(client)` - Authenticate SSE upgrade
|
|
89
|
+
|
|
90
|
+
## Rack Compatibility
|
|
91
|
+
|
|
92
|
+
For Rack apps (objects with `call` method), Iodine automatically:
|
|
93
|
+
- Wraps `call` as `on_http`
|
|
94
|
+
- Builds Rack-compliant `env` hash
|
|
95
|
+
- Handles `rack.hijack` for connection takeover
|
|
96
|
+
- Supports `rack.after_reply` callbacks
|
|
97
|
+
|
|
98
|
+
***************************************************************************** */
|
|
99
|
+
#define H___IODINE_CONNECTION___H
|
|
4
100
|
#include "iodine.h"
|
|
101
|
+
/* *****************************************************************************
|
|
102
|
+
Constants Used only by Iodine::Connection
|
|
103
|
+
***************************************************************************** */
|
|
5
104
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
IODINE_CONNECTION_WEBSOCKET,
|
|
9
|
-
IODINE_CONNECTION_SSE
|
|
10
|
-
} iodine_connection_type_e;
|
|
105
|
+
/** Connection Properties IDs */
|
|
106
|
+
static VALUE IODINE_TIMEOUT_ID;
|
|
11
107
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
108
|
+
/** Cookie SameSite attribute symbol IDs */
|
|
109
|
+
static ID IODINE_SAME_SITE_DEFAULT;
|
|
110
|
+
static ID IODINE_SAME_SITE_NONE;
|
|
111
|
+
static ID IODINE_SAME_SITE_LAX;
|
|
112
|
+
static ID IODINE_SAME_SITE_STRICT;
|
|
113
|
+
|
|
114
|
+
/** TLS backend selection symbol IDs */
|
|
115
|
+
static ID IODINE_TLS_IO_IODINE_ID;
|
|
116
|
+
static ID IODINE_TLS_IO_OPENSSL_ID;
|
|
117
|
+
|
|
118
|
+
/** Cached Rack environment key strings for performance */
|
|
119
|
+
static VALUE IODINE_RACK_CONTENT_LENGTH;
|
|
120
|
+
static VALUE IODINE_RACK_INPUT;
|
|
121
|
+
static VALUE IODINE_RACK_NEORACK_CLIENT;
|
|
122
|
+
static VALUE IODINE_RACK_REQUEST_METHOD;
|
|
123
|
+
static VALUE IODINE_RACK_PATH_INFO;
|
|
124
|
+
static VALUE IODINE_RACK_QUERY_STRING;
|
|
125
|
+
static VALUE IODINE_RACK_SCRIPT_NAME;
|
|
126
|
+
static VALUE IODINE_RACK_SERVER_NAME;
|
|
127
|
+
static VALUE IODINE_RACK_SERVER_PROTOCOL;
|
|
128
|
+
static VALUE IODINE_RACK_HTTP_VERSION;
|
|
129
|
+
static VALUE IODINE_RACK_REMOTE_ADDR;
|
|
130
|
+
|
|
131
|
+
/** Requires 2 allocations instead of 1, quite useless except for testing */
|
|
132
|
+
#define IODINE_CONNECTION_ALLOCATE_SEPARATELY 0
|
|
133
|
+
|
|
134
|
+
/* *****************************************************************************
|
|
135
|
+
Ruby Connection Object - Internal Data Structures
|
|
136
|
+
***************************************************************************** */
|
|
19
137
|
|
|
20
138
|
/**
|
|
21
|
-
*
|
|
139
|
+
* Indices for the connection's Ruby VALUE store array.
|
|
140
|
+
* These are cached Ruby objects associated with each connection.
|
|
22
141
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
142
|
+
typedef enum {
|
|
143
|
+
IODINE_CONNECTION_STORE_handler = 0, /**< Handler object for callbacks */
|
|
144
|
+
IODINE_CONNECTION_STORE_env, /**< Rack environment hash */
|
|
145
|
+
IODINE_CONNECTION_STORE_rack, /**< Rack response array [status,hdrs,body] */
|
|
146
|
+
IODINE_CONNECTION_STORE_method, /**< Cached HTTP method string */
|
|
147
|
+
IODINE_CONNECTION_STORE_path, /**< Cached request path string */
|
|
148
|
+
IODINE_CONNECTION_STORE_opath, /**< Cached original path string */
|
|
149
|
+
IODINE_CONNECTION_STORE_query, /**< Cached query string */
|
|
150
|
+
IODINE_CONNECTION_STORE_version, /**< Cached HTTP version string */
|
|
151
|
+
IODINE_CONNECTION_STORE_tmp, /**< Temporary storage during GC-sensitive ops */
|
|
152
|
+
IODINE_CONNECTION_STORE_FINISH, /**< Sentinel value (array size) */
|
|
153
|
+
} iodine_connection_store_e;
|
|
26
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Connection state flags.
|
|
157
|
+
* These track the connection's current state and type.
|
|
158
|
+
*/
|
|
27
159
|
typedef enum {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
160
|
+
IODINE_CONNECTION_HEADERS_COPIED = 1, /**< Headers copied to Ruby hash */
|
|
161
|
+
IODINE_CONNECTION_UPGRADE_SSE = 2, /**< Connection upgraded to SSE */
|
|
162
|
+
IODINE_CONNECTION_UPGRADE_WS = 4, /**< Connection upgraded to WebSocket */
|
|
163
|
+
IODINE_CONNECTION_UPGRADE = (2 | 4), /**< Any upgrade (SSE or WS) */
|
|
164
|
+
IODINE_CONNECTION_CLOSED = 8, /**< Connection close scheduled */
|
|
165
|
+
IODINE_CONNECTION_CLIENT = 16, /**< This is a client connection */
|
|
166
|
+
} iodine_connection_flags_e;
|
|
35
167
|
|
|
36
168
|
/**
|
|
37
|
-
*
|
|
169
|
+
* The main connection structure wrapping facil.io IO and HTTP handles.
|
|
170
|
+
*
|
|
171
|
+
* This struct is the Ruby-side representation of a connection, containing:
|
|
172
|
+
* - The underlying facil.io IO handle (for raw connections)
|
|
173
|
+
* - The facil.io HTTP handle (for HTTP/WS/SSE connections)
|
|
174
|
+
* - Cached Ruby objects for performance
|
|
175
|
+
* - A minimap for header storage
|
|
176
|
+
* - State flags
|
|
38
177
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
178
|
+
typedef struct iodine_connection_s {
|
|
179
|
+
fio_io_s *io; /**< facil.io IO handle */
|
|
180
|
+
fio_http_s *http; /**< facil.io HTTP handle */
|
|
181
|
+
VALUE store[IODINE_CONNECTION_STORE_FINISH]; /**< Cached Ruby objects */
|
|
182
|
+
iodine_minimap_s map; /**< Header storage map */
|
|
183
|
+
iodine_connection_flags_e flags; /**< Connection state flags */
|
|
184
|
+
} iodine_connection_s;
|
|
42
185
|
|
|
43
|
-
|
|
44
|
-
|
|
186
|
+
static size_t iodine_connection_data_size(const void *ptr_) {
|
|
187
|
+
iodine_connection_s *c = (iodine_connection_s *)ptr_;
|
|
188
|
+
return sizeof(*c) +
|
|
189
|
+
(iodine_hmap_count(&c->map.map) * sizeof(c->map.map.map[0]));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static void iodine_connection_free(void *ptr_) {
|
|
193
|
+
iodine_connection_s *c = (iodine_connection_s *)ptr_;
|
|
194
|
+
fio_http_free(c->http);
|
|
195
|
+
iodine_hmap_destroy(&c->map.map);
|
|
196
|
+
FIO_LEAK_COUNTER_ON_FREE(iodine_connection);
|
|
197
|
+
#if IODINE_CONNECTION_ALLOCATE_SEPARATELY
|
|
198
|
+
fio_free(c);
|
|
199
|
+
#else
|
|
200
|
+
ruby_xfree(c);
|
|
201
|
+
#endif
|
|
202
|
+
}
|
|
45
203
|
|
|
46
|
-
|
|
204
|
+
static void iodine_connection_mark(void *ptr_) {
|
|
205
|
+
iodine_connection_s *c = (iodine_connection_s *)ptr_;
|
|
206
|
+
for (size_t i = 0; i < IODINE_CONNECTION_STORE_FINISH; ++i)
|
|
207
|
+
if (!IODINE_STORE_IS_SKIP(c->store[i]))
|
|
208
|
+
rb_gc_mark(c->store[i]);
|
|
209
|
+
iodine_minimap_gc_mark(&c->map);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static const rb_data_type_t IODINE_CONNECTION_DATA_TYPE = {
|
|
213
|
+
.wrap_struct_name = "IodineConnection",
|
|
214
|
+
.function =
|
|
215
|
+
{
|
|
216
|
+
.dmark = iodine_connection_mark,
|
|
217
|
+
.dfree = iodine_connection_free,
|
|
218
|
+
.dsize = iodine_connection_data_size,
|
|
219
|
+
},
|
|
220
|
+
.data = NULL,
|
|
221
|
+
#if IODINE_CONNECTION_ALLOCATE_SEPARATELY
|
|
222
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
223
|
+
#endif
|
|
224
|
+
};
|
|
47
225
|
|
|
48
|
-
static
|
|
226
|
+
static VALUE iodine_connection_alloc(VALUE klass) {
|
|
227
|
+
#if IODINE_CONNECTION_ALLOCATE_SEPARATELY
|
|
228
|
+
iodine_connection_s *c = (iodine_connection_s *)fio_malloc(sizeof(*c));
|
|
229
|
+
FIO_ASSERT_ALLOC(c);
|
|
230
|
+
VALUE self = TypedData_Wrap_Struct(klass, &IODINE_CONNECTION_DATA_TYPE, c);
|
|
231
|
+
#else
|
|
49
232
|
iodine_connection_s *c = NULL;
|
|
50
|
-
|
|
233
|
+
VALUE self = TypedData_Make_Struct(klass,
|
|
234
|
+
iodine_connection_s,
|
|
235
|
+
&IODINE_CONNECTION_DATA_TYPE,
|
|
236
|
+
c);
|
|
237
|
+
#endif
|
|
238
|
+
*c = (iodine_connection_s){0};
|
|
239
|
+
for (size_t i = 0; i < IODINE_CONNECTION_STORE_FINISH; ++i)
|
|
240
|
+
c->store[i] = Qnil;
|
|
241
|
+
FIO_LEAK_COUNTER_ON_ALLOC(iodine_connection);
|
|
242
|
+
return self;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static iodine_connection_s *iodine_connection_ptr(VALUE self) {
|
|
246
|
+
iodine_connection_s *c;
|
|
247
|
+
TypedData_Get_Struct(self,
|
|
248
|
+
iodine_connection_s,
|
|
249
|
+
&IODINE_CONNECTION_DATA_TYPE,
|
|
51
250
|
c);
|
|
52
251
|
return c;
|
|
53
252
|
}
|
|
54
253
|
|
|
254
|
+
/** Creates (and allocates) a new Iodine::Connection object. */
|
|
255
|
+
static VALUE iodine_connection_create_from_io(fio_io_s *io) {
|
|
256
|
+
VALUE m = rb_obj_alloc(iodine_rb_IODINE_CONNECTION);
|
|
257
|
+
STORE.hold(m);
|
|
258
|
+
iodine_connection_s *c = iodine_connection_ptr(m);
|
|
259
|
+
c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)fio_io_udata(io);
|
|
260
|
+
c->io = io;
|
|
261
|
+
c->http = NULL;
|
|
262
|
+
fio_io_udata_set(io, (void *)m);
|
|
263
|
+
return m;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Creates (and allocates) a new Iodine::Connection object. */
|
|
267
|
+
static VALUE iodine_connection_create_from_http(fio_http_s *h) {
|
|
268
|
+
VALUE m = fio_http_udata2(h)
|
|
269
|
+
? (VALUE)fio_http_udata2(h)
|
|
270
|
+
: iodine_connection_alloc(iodine_rb_IODINE_CONNECTION);
|
|
271
|
+
STORE.hold(m);
|
|
272
|
+
iodine_connection_s *c = iodine_connection_ptr(m);
|
|
273
|
+
c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)fio_http_udata(h);
|
|
274
|
+
c->io = fio_http_io(h);
|
|
275
|
+
if (!(c->flags & IODINE_CONNECTION_CLIENT))
|
|
276
|
+
c->http = fio_http_dup(h);
|
|
277
|
+
fio_http_udata2_set(h, (void *)m);
|
|
278
|
+
return m;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* *****************************************************************************
|
|
282
|
+
State and Misc
|
|
283
|
+
***************************************************************************** */
|
|
284
|
+
|
|
285
|
+
static VALUE iodine_connection_headers_sent_p(VALUE o) {
|
|
286
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
287
|
+
if (!c->http)
|
|
288
|
+
return Qtrue;
|
|
289
|
+
return (fio_http_is_clean(c->http) ? Qfalse : Qtrue);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
static VALUE iodine_connection_is_clean(VALUE o) {
|
|
293
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
294
|
+
if (c->http)
|
|
295
|
+
return (fio_http_is_clean(c->http) ? Qfalse : Qtrue);
|
|
296
|
+
if (c->io)
|
|
297
|
+
return (fio_io_is_open(c->io) ? Qtrue : Qfalse);
|
|
298
|
+
return Qfalse;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static VALUE iodine_connection_dup_failer(VALUE o) {
|
|
302
|
+
rb_raise(rb_eTypeError, "Iodine::Connection objects can't be duplicated.");
|
|
303
|
+
return o;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
static VALUE iodine_connection_to_s(VALUE o) {
|
|
307
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
308
|
+
if (c->http)
|
|
309
|
+
return STORE.frozen_str(FIO_STR_INFO1((char *)"Iodine::Connection(HTTP)"));
|
|
310
|
+
if (c->io)
|
|
311
|
+
return STORE.frozen_str(FIO_STR_INFO1((char *)"Iodine::Connection(RAW)"));
|
|
312
|
+
return STORE.frozen_str(FIO_STR_INFO1((char *)"Iodine::Connection(NONE)"));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
static VALUE iodine_connection_is_websocket(VALUE o) {
|
|
316
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
317
|
+
if (c->http && fio_http_is_websocket(c->http))
|
|
318
|
+
return Qtrue;
|
|
319
|
+
return Qfalse;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static VALUE iodine_connection_is_sse(VALUE o) {
|
|
323
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
324
|
+
if (c->http && fio_http_is_sse(c->http))
|
|
325
|
+
return Qtrue;
|
|
326
|
+
return Qfalse;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
static VALUE iodine_connection_is_http(VALUE o) {
|
|
330
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
331
|
+
if (c->http && !fio_http_is_websocket(c->http) && !fio_http_is_sse(c->http))
|
|
332
|
+
return Qtrue;
|
|
333
|
+
return Qfalse;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static VALUE iodine_connection_is_tls(VALUE o) {
|
|
337
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
338
|
+
if (c->io)
|
|
339
|
+
return fio_io_tls(c->io) ? Qtrue : Qfalse;
|
|
340
|
+
if (c->http)
|
|
341
|
+
return fio_io_tls(fio_http_io(c->http)) ? Qtrue : Qfalse;
|
|
342
|
+
return Qfalse;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* *****************************************************************************
|
|
346
|
+
Ruby Store Get/Set
|
|
347
|
+
***************************************************************************** */
|
|
348
|
+
|
|
349
|
+
#define IODINE_DEF_GET_FUNC(val_name) \
|
|
350
|
+
/** Returns the client's current val_name object. */ \
|
|
351
|
+
static VALUE iodine_connection_##val_name##_get(VALUE self) { \
|
|
352
|
+
iodine_connection_s *c = iodine_connection_ptr(self); \
|
|
353
|
+
return c->store[IODINE_CONNECTION_STORE_##val_name]; \
|
|
354
|
+
}
|
|
355
|
+
#define IODINE_DEF_SET_FUNC(val_name) \
|
|
356
|
+
/** Sets the client's val_name object. */ \
|
|
357
|
+
static VALUE iodine_connection_##val_name##_set(VALUE self, \
|
|
358
|
+
VALUE updated_val) { \
|
|
359
|
+
iodine_connection_s *c = iodine_connection_ptr(self); \
|
|
360
|
+
c->store[IODINE_CONNECTION_STORE_##val_name] = updated_val; \
|
|
361
|
+
return updated_val; \
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
IODINE_DEF_GET_FUNC(handler);
|
|
365
|
+
IODINE_DEF_SET_FUNC(env);
|
|
366
|
+
|
|
367
|
+
#undef IODINE_DEF_GET_FUNC
|
|
368
|
+
#undef IODINE_DEF_SET_FUNC
|
|
369
|
+
|
|
370
|
+
/* *****************************************************************************
|
|
371
|
+
Ruby Store Get/Set HTTP Properties
|
|
372
|
+
***************************************************************************** */
|
|
373
|
+
|
|
374
|
+
#define IODINE_DEF_GETSET_FUNC(val_name, frozen_) \
|
|
375
|
+
/** Returns the client's current val_name object. */ \
|
|
376
|
+
static VALUE iodine_connection_##val_name##_get(VALUE self) { \
|
|
377
|
+
iodine_connection_s *c = iodine_connection_ptr(self); \
|
|
378
|
+
fio_str_info_s setter; \
|
|
379
|
+
if (!c) \
|
|
380
|
+
return Qnil; \
|
|
381
|
+
if (FIO_UNLIKELY(c->http && \
|
|
382
|
+
c->store[IODINE_CONNECTION_STORE_##val_name] == Qnil) && \
|
|
383
|
+
(setter = fio_http_##val_name(c->http)).len) { \
|
|
384
|
+
c->store[IODINE_CONNECTION_STORE_##val_name] = \
|
|
385
|
+
(frozen_ ? STORE.frozen_str(setter) \
|
|
386
|
+
: rb_str_new(setter.buf, setter.len)); \
|
|
387
|
+
} \
|
|
388
|
+
return c->store[IODINE_CONNECTION_STORE_##val_name]; \
|
|
389
|
+
} \
|
|
390
|
+
/** Sets the client's val_name object. */ \
|
|
391
|
+
static VALUE iodine_connection_##val_name##_set(VALUE self, \
|
|
392
|
+
VALUE updated_val) { \
|
|
393
|
+
iodine_connection_s *c = iodine_connection_ptr(self); \
|
|
394
|
+
if (!c) \
|
|
395
|
+
return Qnil; \
|
|
396
|
+
c->store[IODINE_CONNECTION_STORE_##val_name] = updated_val; \
|
|
397
|
+
if (!c->http) \
|
|
398
|
+
return updated_val; \
|
|
399
|
+
if (RB_TYPE_P(updated_val, RUBY_T_SYMBOL)) \
|
|
400
|
+
updated_val = rb_sym2str(updated_val); \
|
|
401
|
+
if (!RB_TYPE_P(updated_val, RUBY_T_STRING)) \
|
|
402
|
+
rb_raise(rb_eTypeError, "new value must be a String or Symbol."); \
|
|
403
|
+
fio_http_##val_name##_set( \
|
|
404
|
+
c->http, \
|
|
405
|
+
FIO_STR_INFO2(RSTRING_PTR(updated_val), \
|
|
406
|
+
(size_t)RSTRING_LEN(updated_val))); \
|
|
407
|
+
return updated_val; \
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* String Get/Set: */
|
|
411
|
+
IODINE_DEF_GETSET_FUNC(method, 1)
|
|
412
|
+
IODINE_DEF_GETSET_FUNC(path, 0)
|
|
413
|
+
IODINE_DEF_GETSET_FUNC(opath, 0)
|
|
414
|
+
IODINE_DEF_GETSET_FUNC(query, 0)
|
|
415
|
+
IODINE_DEF_GETSET_FUNC(version, 1)
|
|
416
|
+
#undef IODINE_DEF_GETSET_FUNC
|
|
417
|
+
|
|
418
|
+
#define IODINE_DEF_GETSET_FUNC(val_name) \
|
|
419
|
+
/** Returns the client's current val_name object. */ \
|
|
420
|
+
static VALUE iodine_connection_##val_name##_get(VALUE self) { \
|
|
421
|
+
iodine_connection_s *c = iodine_connection_ptr(self); \
|
|
422
|
+
if (FIO_UNLIKELY(!c || !c->http)) \
|
|
423
|
+
return Qnil; \
|
|
424
|
+
const size_t setter = fio_http_##val_name(c->http); \
|
|
425
|
+
return ULL2NUM(setter); \
|
|
426
|
+
} \
|
|
427
|
+
/** Sets the client's val_name object. */ \
|
|
428
|
+
static VALUE iodine_connection_##val_name##_set(VALUE self, \
|
|
429
|
+
VALUE updated_val) { \
|
|
430
|
+
iodine_connection_s *c = iodine_connection_ptr(self); \
|
|
431
|
+
size_t setter = 0; \
|
|
432
|
+
if (FIO_UNLIKELY(!c || !c->http)) \
|
|
433
|
+
return Qnil; \
|
|
434
|
+
if (RB_INTEGER_TYPE_P(updated_val)) \
|
|
435
|
+
setter = NUM2ULL(updated_val); \
|
|
436
|
+
else if (RB_TYPE_P(updated_val, RUBY_T_STRING)) { \
|
|
437
|
+
fio_buf_info_s s = IODINE_RSTR_INFO(updated_val); \
|
|
438
|
+
const char *end = s.buf + s.len; \
|
|
439
|
+
setter = fio_atol(&s.buf); \
|
|
440
|
+
if (s.buf != end) \
|
|
441
|
+
rb_raise(rb_eArgError, \
|
|
442
|
+
"passed String isn't a number: %.*s", \
|
|
443
|
+
(int)s.len, \
|
|
444
|
+
end - s.len); \
|
|
445
|
+
} else \
|
|
446
|
+
rb_raise( \
|
|
447
|
+
rb_eTypeError, \
|
|
448
|
+
"new value must be a Number (or a String containing a number)."); \
|
|
449
|
+
fio_http_##val_name##_set(c->http, setter); \
|
|
450
|
+
return ULL2NUM(setter); \
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* Numeral Get/Set: */
|
|
454
|
+
IODINE_DEF_GETSET_FUNC(status)
|
|
455
|
+
#undef IODINE_DEF_GETSET_FUNC
|
|
456
|
+
|
|
457
|
+
/* *****************************************************************************
|
|
458
|
+
Hash Map style access
|
|
459
|
+
***************************************************************************** */
|
|
460
|
+
|
|
461
|
+
FIO_SFUNC int iodine_connection_map_headers_task(fio_http_s *h,
|
|
462
|
+
fio_str_info_s name,
|
|
463
|
+
fio_str_info_s value,
|
|
464
|
+
void *c_) {
|
|
465
|
+
iodine_connection_s *c = (iodine_connection_s *)c_;
|
|
466
|
+
VALUE ary = Qnil;
|
|
467
|
+
VALUE k = STORE.frozen_str(name);
|
|
468
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = k;
|
|
469
|
+
VALUE tmp = iodine_minimap_rb_get(&c->map, k);
|
|
470
|
+
if (FIO_LIKELY(!tmp)) {
|
|
471
|
+
tmp = rb_str_new(value.buf, value.len);
|
|
472
|
+
iodine_hmap_set(&c->map.map, k, tmp, NULL);
|
|
473
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
474
|
+
return 0;
|
|
475
|
+
}
|
|
476
|
+
if (RB_TYPE_P(tmp, RUBY_T_STRING)) {
|
|
477
|
+
ary = rb_ary_new();
|
|
478
|
+
c->store[IODINE_CONNECTION_STORE_rack] = ary; /* unused, so use it */
|
|
479
|
+
rb_ary_push(ary, tmp);
|
|
480
|
+
iodine_hmap_set(&c->map.map, k, ary, NULL);
|
|
481
|
+
c->store[IODINE_CONNECTION_STORE_rack] = Qnil;
|
|
482
|
+
tmp = ary;
|
|
483
|
+
}
|
|
484
|
+
rb_ary_push(tmp, rb_str_new(value.buf, value.len));
|
|
485
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
FIO_SFUNC VALUE iodine_connection_map_headers(VALUE o) {
|
|
490
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
491
|
+
if (!c->http || (fio_atomic_or(&c->flags, IODINE_CONNECTION_HEADERS_COPIED) &
|
|
492
|
+
IODINE_CONNECTION_HEADERS_COPIED))
|
|
493
|
+
return o;
|
|
494
|
+
if ((c->flags & IODINE_CONNECTION_CLIENT))
|
|
495
|
+
goto is_client;
|
|
496
|
+
iodine_hmap_reserve(&c->map.map,
|
|
497
|
+
fio_http_request_header_count(c->http, FIO_STR_INFO0));
|
|
498
|
+
fio_http_request_header_each(c->http, iodine_connection_map_headers_task, c);
|
|
499
|
+
return o;
|
|
500
|
+
|
|
501
|
+
is_client:
|
|
502
|
+
iodine_hmap_reserve(&c->map.map,
|
|
503
|
+
fio_http_response_header_count(c->http, FIO_STR_INFO0));
|
|
504
|
+
fio_http_response_header_each(c->http, iodine_connection_map_headers_task, c);
|
|
505
|
+
fio_http_cookie(c->http, NULL, 0); /* force parsing of cookies */
|
|
506
|
+
return o;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
static iodine_minimap_s *iodine_connection_map_ptr(VALUE self) {
|
|
510
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
511
|
+
return &c->map;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
static VALUE iodine_connection_map_set(VALUE o, VALUE key, VALUE value) {
|
|
515
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
516
|
+
return iodine_minimap_store(&c->map, key, value);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
static VALUE iodine_connection_map_get(VALUE o, VALUE key) {
|
|
520
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
521
|
+
VALUE r = iodine_minimap_rb_get(&c->map, key);
|
|
522
|
+
if (!r)
|
|
523
|
+
goto missing_item;
|
|
524
|
+
return r;
|
|
525
|
+
|
|
526
|
+
missing_item:
|
|
527
|
+
r = Qnil;
|
|
528
|
+
if (!c->http || !RB_TYPE_P(key, RUBY_T_STRING))
|
|
529
|
+
return r;
|
|
530
|
+
if (1 < fio_http_request_header_count(
|
|
531
|
+
c->http,
|
|
532
|
+
(fio_str_info_s)IODINE_RSTR_INFO(key))) { /* more than one */
|
|
533
|
+
r = rb_ary_new();
|
|
534
|
+
iodine_connection_map_set(o, key, r);
|
|
535
|
+
for (size_t i = 0;; ++i) {
|
|
536
|
+
fio_str_info_s h =
|
|
537
|
+
fio_http_request_header(c->http,
|
|
538
|
+
(fio_str_info_s)IODINE_RSTR_INFO(key),
|
|
539
|
+
i);
|
|
540
|
+
if (!h.buf)
|
|
541
|
+
break;
|
|
542
|
+
rb_ary_push(r, rb_str_new(h.buf, h.len));
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
fio_str_info_s h =
|
|
546
|
+
fio_http_request_header(c->http,
|
|
547
|
+
(fio_str_info_s)IODINE_RSTR_INFO(key),
|
|
548
|
+
0);
|
|
549
|
+
if (h.buf)
|
|
550
|
+
r = rb_str_new(h.buf, h.len);
|
|
551
|
+
iodine_connection_map_set(o, key, r);
|
|
552
|
+
}
|
|
553
|
+
return r;
|
|
554
|
+
}
|
|
555
|
+
static VALUE iodine_connection_map_each(VALUE o) {
|
|
556
|
+
iodine_connection_map_headers(o);
|
|
557
|
+
return iodine_minimap_rb_each(iodine_connection_map_ptr(o));
|
|
558
|
+
}
|
|
559
|
+
static VALUE iodine_connection_map_count(VALUE o) {
|
|
560
|
+
return ULL2NUM((unsigned long long)iodine_hmap_count(
|
|
561
|
+
&iodine_connection_map_ptr(o)->map));
|
|
562
|
+
}
|
|
563
|
+
FIO_SFUNC VALUE iodine_connection_map_capa(VALUE o) {
|
|
564
|
+
return ULL2NUM(
|
|
565
|
+
(unsigned long long)iodine_hmap_capa(&iodine_connection_map_ptr(o)->map));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
FIO_SFUNC VALUE iodine_connection_map_reserve(VALUE o, VALUE s_) {
|
|
569
|
+
rb_check_type(s_, T_FIXNUM);
|
|
570
|
+
size_t s = (size_t)NUM2ULL(s_);
|
|
571
|
+
if (s > 0x0FFFFFFFULL)
|
|
572
|
+
rb_raise(
|
|
573
|
+
rb_eRangeError,
|
|
574
|
+
"cannot reserve negative values or values higher than 268,435,455.");
|
|
575
|
+
iodine_minimap_s *m = iodine_connection_map_ptr(o);
|
|
576
|
+
iodine_hmap_reserve(&m->map, s);
|
|
577
|
+
return o;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* *****************************************************************************
|
|
581
|
+
HTTP IO Callbacks and Helpers
|
|
582
|
+
***************************************************************************** */
|
|
583
|
+
|
|
584
|
+
/** Callback for HTTP requests (server) or responses (client). */
|
|
585
|
+
static void iodine_io_http_on_http(fio_http_s *h);
|
|
586
|
+
/** (optional) the callback to be performed when the HTTP service closes. */
|
|
587
|
+
static void iodine_io_http_on_stop(struct fio_http_settings_s *settings);
|
|
588
|
+
|
|
589
|
+
/** Authenticate EventSource (SSE) requests, return non-zero to deny.*/
|
|
590
|
+
static int iodine_io_http_on_authenticate_sse(fio_http_s *h);
|
|
591
|
+
/** Authenticate WebSockets Upgrade requests, return non-zero to deny.*/
|
|
592
|
+
static int iodine_io_http_on_authenticate_websocket(fio_http_s *h);
|
|
593
|
+
|
|
594
|
+
/** Called once a WebSocket / SSE connection upgrade is complete. */
|
|
595
|
+
static void iodine_io_http_on_open(fio_http_s *h);
|
|
596
|
+
|
|
597
|
+
/** Called when a WebSocket message is received. */
|
|
598
|
+
static void iodine_io_http_on_message(fio_http_s *h,
|
|
599
|
+
fio_buf_info_s msg,
|
|
600
|
+
uint8_t is_text);
|
|
601
|
+
/** Called when an EventSource event is received. */
|
|
602
|
+
static void iodine_io_http_on_eventsource(fio_http_s *h,
|
|
603
|
+
fio_buf_info_s id,
|
|
604
|
+
fio_buf_info_s event,
|
|
605
|
+
fio_buf_info_s data);
|
|
606
|
+
/** Called when an EventSource reconnect event requests an ID. */
|
|
607
|
+
static void iodine_io_http_on_eventsource_reconnect(fio_http_s *h,
|
|
608
|
+
fio_buf_info_s id);
|
|
609
|
+
|
|
610
|
+
/** Called for WebSocket / SSE connections when outgoing buffer is empty. */
|
|
611
|
+
static void iodine_io_http_on_ready(fio_http_s *h);
|
|
612
|
+
/** Called for open WebSocket / SSE connections during shutting down. */
|
|
613
|
+
static void iodine_io_http_on_shutdown(fio_http_s *h);
|
|
614
|
+
/** Called after a WebSocket / SSE connection is closed (for cleanup). */
|
|
615
|
+
static void iodine_io_http_on_close(fio_http_s *h);
|
|
616
|
+
/** Called when a request / response cycle is finished with no Upgrade. */
|
|
617
|
+
static void iodine_io_http_on_finish(fio_http_s *h);
|
|
618
|
+
|
|
619
|
+
/* main argument structure */
|
|
620
|
+
typedef struct {
|
|
621
|
+
fio_buf_info_s url;
|
|
622
|
+
fio_url_s url_data;
|
|
623
|
+
VALUE rb_tls;
|
|
624
|
+
fio_buf_info_s hint;
|
|
625
|
+
VALUE headers;
|
|
626
|
+
VALUE cookies;
|
|
627
|
+
fio_buf_info_s method;
|
|
628
|
+
fio_buf_info_s body;
|
|
629
|
+
fio_http_settings_s settings;
|
|
630
|
+
enum {
|
|
631
|
+
IODINE_SERVICE_RAW,
|
|
632
|
+
IODINE_SERVICE_HTTP,
|
|
633
|
+
IODINE_SERVICE_WS,
|
|
634
|
+
IODINE_SERVICE_UNKNOWN,
|
|
635
|
+
} service;
|
|
636
|
+
} iodine_connection_args_s;
|
|
637
|
+
|
|
638
|
+
static void iodine_tcp_on_stop(fio_io_protocol_s *p, void *udata);
|
|
639
|
+
static void *iodine_tcp_listen(iodine_connection_args_s args);
|
|
640
|
+
|
|
641
|
+
/* *****************************************************************************
|
|
642
|
+
HTTP Cookies
|
|
643
|
+
***************************************************************************** */
|
|
644
|
+
|
|
645
|
+
FIO_SFUNC VALUE iodine_connection_cookie_get(VALUE o, VALUE name) {
|
|
646
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
647
|
+
if (!c->http) {
|
|
648
|
+
rb_raise(rb_eTypeError,
|
|
649
|
+
"Iodine::Connection instance should be an HTTP connection to "
|
|
650
|
+
"use cookies.");
|
|
651
|
+
}
|
|
652
|
+
if (RB_TYPE_P(name, RUBY_T_SYMBOL))
|
|
653
|
+
name = rb_sym_to_s(name);
|
|
654
|
+
rb_check_type(name, RUBY_T_STRING);
|
|
655
|
+
fio_str_info_s str =
|
|
656
|
+
fio_http_cookie(c->http, RSTRING_PTR(name), RSTRING_LEN(name));
|
|
657
|
+
if (!str.buf)
|
|
658
|
+
return Qnil;
|
|
659
|
+
return rb_enc_str_new(str.buf, str.len, IodineBinaryEncoding);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
FIO_SFUNC VALUE iodine_connection_cookie_set(int argc, VALUE *argv, VALUE o) {
|
|
663
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
664
|
+
if (!c->http) {
|
|
665
|
+
rb_raise(rb_eTypeError,
|
|
666
|
+
"Iodine::Connection instance should be an HTTP connection to "
|
|
667
|
+
"use cookies.");
|
|
668
|
+
}
|
|
669
|
+
fio_str_info_s name = FIO_STR_INFO0;
|
|
670
|
+
fio_str_info_s value = FIO_STR_INFO0;
|
|
671
|
+
size_t max_age = 0;
|
|
672
|
+
fio_str_info_s domain = FIO_STR_INFO0;
|
|
673
|
+
fio_str_info_s path = FIO_STR_INFO0;
|
|
674
|
+
VALUE same_site_rb = Qnil;
|
|
675
|
+
uint8_t secure = 0;
|
|
676
|
+
uint8_t http_only = 0;
|
|
677
|
+
uint8_t partitioned = 0;
|
|
678
|
+
|
|
679
|
+
fio_http_cookie_same_site_e same_site_c =
|
|
680
|
+
FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT;
|
|
681
|
+
iodine_rb2c_arg(argc,
|
|
682
|
+
argv,
|
|
683
|
+
IODINE_ARG_STR(name, 0, "name", 1),
|
|
684
|
+
IODINE_ARG_STR(value, 0, "value", 0),
|
|
685
|
+
IODINE_ARG_SIZE_T(max_age, 0, "max_age", 0),
|
|
686
|
+
IODINE_ARG_STR(domain, 0, "domain", 0),
|
|
687
|
+
IODINE_ARG_STR(path, 0, "path", 0),
|
|
688
|
+
IODINE_ARG_RB(same_site_rb, 0, "same_site", 0),
|
|
689
|
+
IODINE_ARG_BOOL(secure, 0, "secure", 0),
|
|
690
|
+
IODINE_ARG_BOOL(http_only, 0, "http_only", 0),
|
|
691
|
+
IODINE_ARG_BOOL(partitioned, 0, "partitioned", 0));
|
|
692
|
+
if (!name.len)
|
|
693
|
+
return Qnil;
|
|
694
|
+
if (same_site_rb != Qnil) {
|
|
695
|
+
rb_check_type(same_site_rb, RUBY_T_SYMBOL);
|
|
696
|
+
ID same_site_id = rb_sym2id(same_site_rb);
|
|
697
|
+
if (same_site_id == IODINE_SAME_SITE_DEFAULT)
|
|
698
|
+
same_site_c = FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT;
|
|
699
|
+
else if (same_site_id == IODINE_SAME_SITE_NONE)
|
|
700
|
+
same_site_c = FIO_HTTP_COOKIE_SAME_SITE_NONE;
|
|
701
|
+
else if (same_site_id == IODINE_SAME_SITE_LAX)
|
|
702
|
+
same_site_c = FIO_HTTP_COOKIE_SAME_SITE_LAX;
|
|
703
|
+
else if (same_site_id == IODINE_SAME_SITE_STRICT)
|
|
704
|
+
same_site_c = FIO_HTTP_COOKIE_SAME_SITE_STRICT;
|
|
705
|
+
}
|
|
706
|
+
if (fio_http_cookie_set(c->http,
|
|
707
|
+
.name = name,
|
|
708
|
+
.value = value,
|
|
709
|
+
.domain = domain,
|
|
710
|
+
.path = path,
|
|
711
|
+
.max_age = (int)max_age,
|
|
712
|
+
.same_site = (fio_http_cookie_same_site_e)same_site_c,
|
|
713
|
+
.secure = secure,
|
|
714
|
+
.http_only = http_only,
|
|
715
|
+
.partitioned = partitioned)) {
|
|
716
|
+
return Qfalse; /* NeoRack spects - return false, not exceptions */
|
|
717
|
+
// rb_raise(rb_eRuntimeError,
|
|
718
|
+
// "cookies cannot be set at this point, please check if headers "
|
|
719
|
+
// "were sent before setting a cookie.");
|
|
720
|
+
}
|
|
721
|
+
return Qtrue;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
static int iodine_connection_cookie_each_task(fio_http_s *h,
|
|
725
|
+
fio_str_info_s name,
|
|
726
|
+
fio_str_info_s value,
|
|
727
|
+
void *info) {
|
|
728
|
+
VALUE *argv = (VALUE *)info;
|
|
729
|
+
++argv;
|
|
730
|
+
argv[0] = rb_enc_str_new(name.buf, name.len, IodineBinaryEncoding);
|
|
731
|
+
STORE.cache(argv[0]);
|
|
732
|
+
argv[1] = rb_enc_str_new(value.buf, value.len, IodineBinaryEncoding);
|
|
733
|
+
STORE.cache(argv[1]);
|
|
734
|
+
rb_yield_values2(2, argv);
|
|
735
|
+
argv[0] = Qnil;
|
|
736
|
+
argv[1] = Qnil;
|
|
737
|
+
return 0;
|
|
738
|
+
(void)h;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
FIO_SFUNC VALUE iodine_connection_cookie_each_caller(VALUE info_) {
|
|
742
|
+
VALUE *info = (VALUE *)info_;
|
|
743
|
+
iodine_connection_s *c = iodine_connection_ptr(info[0]);
|
|
744
|
+
fio_http_cookie_each(c->http, iodine_connection_cookie_each_task, info);
|
|
745
|
+
return info_;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
FIO_SFUNC VALUE iodine_connection_cookie_each(VALUE o) {
|
|
749
|
+
rb_block_given_p();
|
|
750
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
751
|
+
if (!c->http) {
|
|
752
|
+
rb_raise(rb_eTypeError,
|
|
753
|
+
"Iodine::Connection instance should be an HTTP connection to "
|
|
754
|
+
"use cookies.");
|
|
755
|
+
}
|
|
756
|
+
VALUE info[3] = {o, Qnil, Qnil};
|
|
757
|
+
int state = 0;
|
|
758
|
+
rb_protect(iodine_connection_cookie_each_caller, (VALUE)info, &state);
|
|
759
|
+
if (!state)
|
|
760
|
+
return o;
|
|
761
|
+
STORE.release(info[2]);
|
|
762
|
+
STORE.release(info[1]);
|
|
763
|
+
VALUE exc = rb_errinfo();
|
|
764
|
+
if (!rb_obj_is_instance_of(exc, rb_eLocalJumpError))
|
|
765
|
+
iodine_handle_exception(NULL);
|
|
766
|
+
rb_set_errinfo(Qnil);
|
|
767
|
+
return o;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* *****************************************************************************
|
|
771
|
+
HTTP Body Access
|
|
772
|
+
***************************************************************************** */
|
|
773
|
+
|
|
774
|
+
FIO_SFUNC VALUE iodine_connection_body_length(VALUE o) {
|
|
775
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
776
|
+
if (!c->http)
|
|
777
|
+
return ULL2NUM(0);
|
|
778
|
+
return ULL2NUM(fio_http_body_length(c->http));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
FIO_SFUNC VALUE iodine_connection_body_gets(int argc, VALUE *argv, VALUE o) {
|
|
782
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
783
|
+
if (!c->http)
|
|
784
|
+
return Qnil;
|
|
785
|
+
size_t length = (size_t)-1;
|
|
786
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_SIZE_T(length, 0, "limit", 0));
|
|
787
|
+
fio_str_info_s s = fio_http_body_read_until(c->http, '\n', length);
|
|
788
|
+
if (!s.len)
|
|
789
|
+
return Qnil;
|
|
790
|
+
return rb_enc_str_new(s.buf, s.len, IodineBinaryEncoding);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
FIO_SFUNC VALUE iodine_connection_body_read(int argc, VALUE *argv, VALUE o) {
|
|
794
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
795
|
+
if (!c->http)
|
|
796
|
+
return Qnil;
|
|
797
|
+
size_t length = (size_t)-1;
|
|
798
|
+
VALUE rbuf = Qnil;
|
|
799
|
+
iodine_rb2c_arg(argc,
|
|
800
|
+
argv,
|
|
801
|
+
IODINE_ARG_SIZE_T(length, 0, "maxlen", 0),
|
|
802
|
+
IODINE_ARG_RB(rbuf, 0, "out_string", 0));
|
|
803
|
+
if (rbuf != Qnil) {
|
|
804
|
+
Check_Type(rbuf, T_STRING);
|
|
805
|
+
rb_str_set_len(rbuf, 0);
|
|
806
|
+
}
|
|
807
|
+
fio_str_info_s s = fio_http_body_read(c->http, length);
|
|
808
|
+
if (s.len) {
|
|
809
|
+
if (rbuf == Qnil)
|
|
810
|
+
rbuf = rb_enc_str_new(s.buf, s.len, IodineBinaryEncoding);
|
|
811
|
+
else
|
|
812
|
+
rb_str_cat(rbuf, s.buf, s.len);
|
|
813
|
+
}
|
|
814
|
+
return rbuf;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
FIO_SFUNC VALUE iodine_connection_body_seek(int argc, VALUE *argv, VALUE o) {
|
|
818
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
819
|
+
if (!c->http)
|
|
820
|
+
return Qnil;
|
|
821
|
+
int64_t pos = SSIZE_MAX;
|
|
822
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_NUM(pos, 0, "pos", 0));
|
|
823
|
+
pos = (long long)fio_http_body_seek(c->http, (ssize_t)pos);
|
|
824
|
+
return ULL2NUM(pos);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
FIO_SFUNC VALUE iodine_connection_body_close(VALUE o) {
|
|
828
|
+
VALUE r = Qtrue;
|
|
829
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
830
|
+
if (!c->http)
|
|
831
|
+
return Qnil;
|
|
832
|
+
if (!fio_http_body_length(c->http))
|
|
833
|
+
r = Qfalse;
|
|
834
|
+
fio_http_body_close(c->http);
|
|
835
|
+
return r;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/* *****************************************************************************
|
|
839
|
+
Lower-Case Helper for Header Names
|
|
840
|
+
***************************************************************************** */
|
|
841
|
+
|
|
842
|
+
#define IODINE___COPY_TO_LOWER_CASE(var_name, inpute_var) \
|
|
843
|
+
FIO_STR_INFO_TMP_VAR(var_name, 4096); \
|
|
844
|
+
iodine___copy_to_lower_case(&var_name, &inpute_var);
|
|
845
|
+
|
|
846
|
+
/** Converts a Header key to lower-case */
|
|
847
|
+
FIO_IFUNC void iodine___copy_to_lower_case(fio_str_info_s *t,
|
|
848
|
+
fio_str_info_s *k) {
|
|
849
|
+
if (k->len >= t->capa)
|
|
850
|
+
goto too_big;
|
|
851
|
+
for (size_t i = 0; i < k->len; ++i) {
|
|
852
|
+
uint8_t c = (uint8_t)k->buf[i];
|
|
853
|
+
c |= (uint8_t)((c >= 'A' && c <= 'Z') << 5);
|
|
854
|
+
t->buf[i] = c;
|
|
855
|
+
}
|
|
856
|
+
t->len = k->len;
|
|
857
|
+
return;
|
|
858
|
+
too_big:
|
|
859
|
+
*t = *k;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/* *****************************************************************************
|
|
863
|
+
Default Handler Implementations
|
|
864
|
+
***************************************************************************** */
|
|
865
|
+
|
|
866
|
+
static VALUE iodine_handler_default_on_http_rest(VALUE handler, VALUE client) {
|
|
867
|
+
iodine_connection_s *c = iodine_connection_ptr(client);
|
|
868
|
+
if (FIO_UNLIKELY(!c || (VALUE)c == Qnil)) {
|
|
869
|
+
FIO_LOG_FATAL("`on_http` couldn't allocate Iodine::Connection object!");
|
|
870
|
+
return Qnil;
|
|
871
|
+
}
|
|
872
|
+
if (!c->http)
|
|
873
|
+
return Qnil;
|
|
874
|
+
fio_http_s *h = c->http;
|
|
875
|
+
ID callback = 0;
|
|
876
|
+
switch (fio_http_resource_action(h)) {
|
|
877
|
+
case FIO_HTTP_RESOURCE_NONE: callback = IODINE_ON_HTTP_ID; break;
|
|
878
|
+
case FIO_HTTP_RESOURCE_INDEX: callback = IODINE_INDEX_ID; break;
|
|
879
|
+
case FIO_HTTP_RESOURCE_SHOW: callback = IODINE_SHOW_ID; break;
|
|
880
|
+
case FIO_HTTP_RESOURCE_NEW: callback = IODINE_NEW_ID; break;
|
|
881
|
+
case FIO_HTTP_RESOURCE_EDIT: callback = IODINE_EDIT_ID; break;
|
|
882
|
+
case FIO_HTTP_RESOURCE_CREATE: callback = IODINE_CREATE_ID; break;
|
|
883
|
+
case FIO_HTTP_RESOURCE_UPDATE: callback = IODINE_UPDATE_ID; break;
|
|
884
|
+
case FIO_HTTP_RESOURCE_DELETE: callback = IODINE_DELETE_ID; break;
|
|
885
|
+
case FIO_HTTP_RESOURCE_QUERY: callback = IODINE_QUERY_ID; break;
|
|
886
|
+
}
|
|
887
|
+
rb_funcallv(handler, callback, 1, &client);
|
|
888
|
+
return Qnil;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
static VALUE iodine_handler_default_on_http404(VALUE handler, VALUE client) {
|
|
892
|
+
iodine_connection_s *c = iodine_connection_ptr(client);
|
|
893
|
+
if (!c->http)
|
|
894
|
+
return Qnil;
|
|
895
|
+
fio_http_send_error_response(c->http, 404);
|
|
896
|
+
return Qnil;
|
|
897
|
+
(void)handler;
|
|
898
|
+
}
|
|
899
|
+
static VALUE iodine_handler_default_on_http405(VALUE handler, VALUE client) {
|
|
900
|
+
iodine_connection_s *c = iodine_connection_ptr(client);
|
|
901
|
+
if (!c->http)
|
|
902
|
+
return Qnil;
|
|
903
|
+
fio_http_send_error_response(c->http, 405);
|
|
904
|
+
return Qnil;
|
|
905
|
+
(void)handler;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/* by default, QUERY requests get routed to the `index` method. */
|
|
909
|
+
static VALUE iodine_handler_default_on_http_query(VALUE handler, VALUE client) {
|
|
910
|
+
return rb_funcallv(handler, IODINE_INDEX_ID, 1, &client);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
static VALUE iodine_handler_default_on_event(VALUE handler, VALUE client) {
|
|
914
|
+
return Qnil;
|
|
915
|
+
(void)handler, (void)client;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
static VALUE iodine_handler_default_on_auth(VALUE handler, VALUE client) {
|
|
919
|
+
return Qtrue;
|
|
920
|
+
(void)handler, (void)client;
|
|
921
|
+
}
|
|
922
|
+
static VALUE iodine_handler_default_on_authx(VALUE handler, VALUE client) {
|
|
923
|
+
return Qfalse;
|
|
924
|
+
(void)handler, (void)client;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
static VALUE iodine_handler_on_auth_reroute(VALUE handler, VALUE client) {
|
|
928
|
+
iodine_caller_result_s r =
|
|
929
|
+
iodine_ruby_call_inside(handler, IODINE_ON_AUTHENTICATE_ID, 1, &client);
|
|
930
|
+
if (r.exception || r.result != Qtrue)
|
|
931
|
+
return Qfalse;
|
|
932
|
+
return Qtrue;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
static VALUE iodine_handler_default_on_message(VALUE h, VALUE c, VALUE m) {
|
|
936
|
+
return Qnil;
|
|
937
|
+
(void)h, (void)c, (void)m;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
static void iodine_connection___add_header(fio_http_s *h,
|
|
941
|
+
fio_str_info_s n,
|
|
942
|
+
fio_str_info_s v) {
|
|
943
|
+
char *eol = (char *)FIO_MEMCHR(v.buf, '\n', v.len);
|
|
944
|
+
while (eol) {
|
|
945
|
+
fio_str_info_s tmp = FIO_STR_INFO2(v.buf, (size_t)(eol - v.buf));
|
|
946
|
+
++eol;
|
|
947
|
+
v.len -= tmp.len + 1;
|
|
948
|
+
v.buf = eol;
|
|
949
|
+
tmp.len -= (uint8_t)(tmp.len && tmp.buf[tmp.len - 1] == '\r');
|
|
950
|
+
fio_http_response_header_add(h, n, tmp);
|
|
951
|
+
eol = (char *)FIO_MEMCHR(v.buf, '\n', v.len);
|
|
952
|
+
}
|
|
953
|
+
fio_http_response_header_add(h, n, v);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
static int iodine_handler_default_on_http__header2(fio_str_info_s n,
|
|
957
|
+
VALUE v,
|
|
958
|
+
VALUE h_) {
|
|
959
|
+
fio_http_s *h = (fio_http_s *)h_;
|
|
960
|
+
FIO_STR_INFO_TMP_VAR(vstr, 24);
|
|
961
|
+
if (RB_TYPE_P(v, RUBY_T_ARRAY))
|
|
962
|
+
goto is_array;
|
|
963
|
+
if (RB_TYPE_P(v, RUBY_T_SYMBOL))
|
|
964
|
+
v = rb_sym_to_s(v);
|
|
965
|
+
else if (RB_TYPE_P(v, RUBY_T_BIGNUM)) /* Bignum (Windows: Integers >= 2^30) */
|
|
966
|
+
v = rb_big2str(v, 10);
|
|
967
|
+
if (RB_TYPE_P(v, RUBY_T_STRING))
|
|
968
|
+
vstr = (fio_str_info_s)IODINE_RSTR_INFO(v);
|
|
969
|
+
else if (RB_TYPE_P(v, RUBY_T_FIXNUM))
|
|
970
|
+
fio_string_write_i(&vstr, NULL, (int64_t)RB_NUM2LL(v));
|
|
971
|
+
else
|
|
972
|
+
goto type_error;
|
|
973
|
+
iodine_connection___add_header(h, n, vstr);
|
|
974
|
+
return ST_CONTINUE;
|
|
975
|
+
|
|
976
|
+
is_array:
|
|
977
|
+
for (size_t i = 0; i < (size_t)RARRAY_LEN(v); ++i) {
|
|
978
|
+
VALUE t = RARRAY_PTR(v)[i];
|
|
979
|
+
if (!RB_TYPE_P(t, RUBY_T_STRING))
|
|
980
|
+
continue;
|
|
981
|
+
iodine_connection___add_header(h, n, (fio_str_info_s)IODINE_RSTR_INFO(t));
|
|
982
|
+
}
|
|
983
|
+
return ST_CONTINUE;
|
|
984
|
+
|
|
985
|
+
type_error:
|
|
986
|
+
FIO_LOG_WARNING("Rack response contained an invalid header value type. "
|
|
987
|
+
"Header values MUST be Strings (or Arrays of Strings)");
|
|
988
|
+
return ST_CONTINUE;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
static int iodine_handler_default_on_http__header(VALUE n_, VALUE v, VALUE h_) {
|
|
992
|
+
fio_str_info_s header_rb;
|
|
993
|
+
if (RB_TYPE_P(n_, RUBY_T_SYMBOL))
|
|
994
|
+
n_ = rb_sym_to_s(n_);
|
|
995
|
+
if (!RB_TYPE_P(n_, RUBY_T_STRING))
|
|
996
|
+
return ST_CONTINUE;
|
|
997
|
+
header_rb = (fio_str_info_s)IODINE_RSTR_INFO(n_);
|
|
998
|
+
IODINE___COPY_TO_LOWER_CASE(n, header_rb);
|
|
999
|
+
if (n.len > 5 && n.buf[4] == '.' && /* ignore "rack.<name>" headers */
|
|
1000
|
+
fio_buf2u32u("rack") == fio_buf2u32u(n.buf))
|
|
1001
|
+
return ST_CONTINUE;
|
|
1002
|
+
return iodine_handler_default_on_http__header2(n, v, h_);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
typedef struct {
|
|
1006
|
+
fio_http_s *h;
|
|
1007
|
+
char *out;
|
|
1008
|
+
} iodine_body_each_info_s;
|
|
1009
|
+
|
|
1010
|
+
static VALUE iodine_handler_default_on_http__each_body(VALUE s, VALUE info_) {
|
|
1011
|
+
iodine_body_each_info_s *i = (iodine_body_each_info_s *)info_;
|
|
1012
|
+
if (!RB_TYPE_P(s, RUBY_T_STRING))
|
|
1013
|
+
s = rb_any_to_s(s);
|
|
1014
|
+
if (!RB_TYPE_P(s, RUBY_T_STRING))
|
|
1015
|
+
goto error_in_type;
|
|
1016
|
+
i->out = fio_bstr_write(i->out, RSTRING_PTR(s), (size_t)RSTRING_LEN(s));
|
|
1017
|
+
// fio_http_write(h,
|
|
1018
|
+
// .buf = RSTRING_PTR(s),
|
|
1019
|
+
// .len = (size_t)RSTRING_LEN(s),
|
|
1020
|
+
// .copy = 1,
|
|
1021
|
+
// .finish = 0);
|
|
1022
|
+
return s;
|
|
1023
|
+
|
|
1024
|
+
error_in_type:
|
|
1025
|
+
if (s != Qnil)
|
|
1026
|
+
FIO_LOG_ERROR(
|
|
1027
|
+
"HTTP response body .each must be yield only String Objects.");
|
|
1028
|
+
return s;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
static VALUE iodine_connection_env_get(VALUE self);
|
|
1032
|
+
static VALUE iodine_connection_handler_set(VALUE client, VALUE handler);
|
|
1033
|
+
static VALUE iodine_connection_rack_hijack(VALUE self);
|
|
1034
|
+
static int iodine_connection_rack_hijack_partial(iodine_connection_s *c,
|
|
1035
|
+
VALUE proc);
|
|
1036
|
+
static VALUE iodine_connection_peer_addr(VALUE self);
|
|
1037
|
+
static VALUE iodine_handler_default_on_http(VALUE handler, VALUE client) {
|
|
1038
|
+
/* RACK specification: https://github.com/rack/rack/blob/main/SPEC.rdoc */
|
|
1039
|
+
VALUE returned_value = Qnil;
|
|
1040
|
+
VALUE partial_hijack = Qnil;
|
|
1041
|
+
iodine_connection_s *c = iodine_connection_ptr(client);
|
|
1042
|
+
if (!c->http)
|
|
1043
|
+
return returned_value;
|
|
1044
|
+
VALUE r, env;
|
|
1045
|
+
bool should_finish = 1;
|
|
1046
|
+
/* collect `env` and call `call`. */
|
|
1047
|
+
env = iodine_connection_env_get(client);
|
|
1048
|
+
/* call `call` from top of MiddleWare / App chain */
|
|
1049
|
+
r = c->store[IODINE_CONNECTION_STORE_rack] =
|
|
1050
|
+
rb_funcallv(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1051
|
+
IODINE_CALL_ID,
|
|
1052
|
+
1,
|
|
1053
|
+
&env);
|
|
1054
|
+
if (!c->http)
|
|
1055
|
+
return returned_value;
|
|
1056
|
+
if (!RB_TYPE_P(r, RUBY_T_ARRAY))
|
|
1057
|
+
goto rack_error;
|
|
1058
|
+
if (RARRAY_LEN(r) < 3)
|
|
1059
|
+
goto rack_error;
|
|
1060
|
+
{ /* handle status */
|
|
1061
|
+
VALUE s = RARRAY_PTR(r)[0];
|
|
1062
|
+
long long i = 0;
|
|
1063
|
+
switch (rb_type(s)) {
|
|
1064
|
+
case RUBY_T_STRING: i = rb_str_to_inum(s, 10, 0); break;
|
|
1065
|
+
case RUBY_T_FIXNUM: i = NUM2LL(s); break;
|
|
1066
|
+
case RUBY_T_TRUE: i = 200; break;
|
|
1067
|
+
case RUBY_T_FALSE: i = 404; break;
|
|
1068
|
+
default: i = 0;
|
|
1069
|
+
}
|
|
1070
|
+
if (i > 0 && i < 1000)
|
|
1071
|
+
fio_http_status_set(c->http, (size_t)i);
|
|
1072
|
+
}
|
|
1073
|
+
{ /* handle headers */
|
|
1074
|
+
VALUE hdr = RARRAY_PTR(r)[1];
|
|
1075
|
+
if (RB_TYPE_P(hdr, RUBY_T_HASH)) {
|
|
1076
|
+
partial_hijack = rb_hash_delete(hdr, IODINE_RACK_HIJACK_STR);
|
|
1077
|
+
should_finish = (partial_hijack == Qnil);
|
|
1078
|
+
rb_hash_foreach(hdr,
|
|
1079
|
+
iodine_handler_default_on_http__header,
|
|
1080
|
+
(VALUE)c->http);
|
|
1081
|
+
} else if (RB_TYPE_P(hdr, RUBY_T_ARRAY)) {
|
|
1082
|
+
for (size_t i = 0; i < (size_t)RARRAY_LEN(hdr); ++i) {
|
|
1083
|
+
VALUE t = RARRAY_PTR(hdr)[i];
|
|
1084
|
+
if (!RB_TYPE_P(t, RUBY_T_ARRAY) || RARRAY_LEN(t) != 2 ||
|
|
1085
|
+
!RB_TYPE_P(RARRAY_PTR(t)[0], RUBY_T_STRING))
|
|
1086
|
+
goto rack_error;
|
|
1087
|
+
fio_str_info_s hn = (fio_str_info_s)IODINE_RSTR_INFO(RARRAY_PTR(t)[0]);
|
|
1088
|
+
if (!FIO_STR_INFO_IS_EQ(
|
|
1089
|
+
(fio_buf_info_s)IODINE_RSTR_INFO(RARRAY_PTR(t)[0]),
|
|
1090
|
+
(fio_buf_info_s)IODINE_RSTR_INFO(IODINE_RACK_HIJACK_STR))) {
|
|
1091
|
+
iodine_connection___add_header(
|
|
1092
|
+
c->http,
|
|
1093
|
+
hn,
|
|
1094
|
+
(fio_str_info_s)IODINE_RSTR_INFO(RARRAY_PTR(t)[1]));
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
partial_hijack = RARRAY_PTR(t)[1];
|
|
1098
|
+
should_finish = (partial_hijack == Qnil);
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
FIO_LOG_ERROR("Rack application response headers type error");
|
|
1102
|
+
goto rack_error;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (!(c->flags & IODINE_CONNECTION_UPGRADE)) { /* handle body */
|
|
1106
|
+
VALUE bd = RARRAY_PTR(r)[2];
|
|
1107
|
+
if (rb_respond_to(bd, IODINE_EACH_ID)) { /* enumerable body... */
|
|
1108
|
+
if (rb_respond_to(bd, IODINE_TO_PATH_ID)) { /* named file body... */
|
|
1109
|
+
VALUE p = rb_funcallv(bd, IODINE_TO_PATH_ID, 0, NULL);
|
|
1110
|
+
if (!RB_TYPE_P(p, RUBY_T_STRING))
|
|
1111
|
+
goto rack_error; /* FIXME? something else? */
|
|
1112
|
+
if (fio_http_static_file_response(c->http,
|
|
1113
|
+
(fio_str_info_s)IODINE_RSTR_INFO(p),
|
|
1114
|
+
FIO_STR_INFO0,
|
|
1115
|
+
fio_http_settings(c->http)->max_age))
|
|
1116
|
+
goto rack_error; /* FIXME? something else? */
|
|
1117
|
+
} else {
|
|
1118
|
+
iodine_body_each_info_s each = {.h = c->http};
|
|
1119
|
+
rb_block_call(
|
|
1120
|
+
bd,
|
|
1121
|
+
IODINE_EACH_ID,
|
|
1122
|
+
0,
|
|
1123
|
+
NULL,
|
|
1124
|
+
(rb_block_call_func_t)iodine_handler_default_on_http__each_body,
|
|
1125
|
+
(VALUE)&each);
|
|
1126
|
+
fio_http_write(c->http,
|
|
1127
|
+
.buf = each.out,
|
|
1128
|
+
.len = fio_bstr_len(each.out),
|
|
1129
|
+
.dealloc = (void (*)(void *))fio_bstr_free,
|
|
1130
|
+
.finish = should_finish);
|
|
1131
|
+
}
|
|
1132
|
+
} else if (RB_TYPE_P(bd, RUBY_T_STRING)) { /* a simple String */
|
|
1133
|
+
fio_http_write(c->http,
|
|
1134
|
+
.buf = RSTRING_PTR(bd),
|
|
1135
|
+
.len = (size_t)RSTRING_LEN(bd),
|
|
1136
|
+
.copy = 1,
|
|
1137
|
+
.finish = should_finish);
|
|
1138
|
+
} else if (bd == Qnil) { /* do nothing? no body. */
|
|
1139
|
+
fio_http_write(c->http, .finish = should_finish);
|
|
1140
|
+
} else { /* streaming body – answers to `call` */
|
|
1141
|
+
partial_hijack = bd;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (partial_hijack && partial_hijack != Qnil &&
|
|
1145
|
+
iodine_connection_rack_hijack_partial(c, partial_hijack))
|
|
1146
|
+
goto rack_error;
|
|
1147
|
+
|
|
1148
|
+
rb_check_funcall(RARRAY_PTR(r)[2], IODINE_CLOSE_ID, 0, NULL);
|
|
1149
|
+
|
|
1150
|
+
after_reply:
|
|
1151
|
+
|
|
1152
|
+
r = rb_hash_aref(env, IODINE_RACK_AFTER_RPLY_STR);
|
|
1153
|
+
if (RB_TYPE_P(r, RUBY_T_ARRAY))
|
|
1154
|
+
for (size_t i = 0; i < (size_t)RARRAY_LEN(r); ++i) {
|
|
1155
|
+
// rb_funcallv(RARRAY_PTR(r)[i], IODINE_CALL_ID, 0, NULL);
|
|
1156
|
+
IODINE_DEFER_BLOCK(RARRAY_PTR(r)[i]);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
c->store[IODINE_CONNECTION_STORE_rack] = Qnil;
|
|
1160
|
+
return returned_value;
|
|
1161
|
+
|
|
1162
|
+
rack_error:
|
|
1163
|
+
if (RB_TYPE_P(r, RUBY_T_ARRAY) && RARRAY_LEN(r) >= 3)
|
|
1164
|
+
rb_check_funcall(RARRAY_PTR(r)[2], IODINE_CLOSE_ID, 0, NULL);
|
|
1165
|
+
fio_http_send_error_response(c->http, 500);
|
|
1166
|
+
c->store[IODINE_CONNECTION_STORE_rack] = Qnil;
|
|
1167
|
+
goto after_reply;
|
|
1168
|
+
(void)handler;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
static VALUE iodine_handler_on_auth_rack_internal(VALUE handler,
|
|
1172
|
+
VALUE client,
|
|
1173
|
+
VALUE upgrd_sym) {
|
|
1174
|
+
VALUE env = iodine_connection_env_get(client);
|
|
1175
|
+
rb_hash_aset(env, IODINE_RACK_UPGRADE_Q_STR, upgrd_sym);
|
|
1176
|
+
iodine_handler_default_on_http(handler, client);
|
|
1177
|
+
/* test old-style iodine Upgrade approach */
|
|
1178
|
+
VALUE hn = rb_hash_aref(env, IODINE_RACK_UPGRADE_STR);
|
|
1179
|
+
if (hn != Qnil) {
|
|
1180
|
+
iodine_connection_s *c = iodine_connection_ptr(client);
|
|
1181
|
+
if (hn != Qtrue && hn != c->store[IODINE_CONNECTION_STORE_handler])
|
|
1182
|
+
iodine_connection_handler_set(client, hn);
|
|
1183
|
+
return Qtrue;
|
|
1184
|
+
}
|
|
1185
|
+
return Qnil;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
static VALUE iodine_handler_on_auth_rack_ws(VALUE handler, VALUE client) {
|
|
1189
|
+
return iodine_handler_on_auth_rack_internal(handler,
|
|
1190
|
+
client,
|
|
1191
|
+
IODINE_RACK_UPGRADE_WS_SYM);
|
|
1192
|
+
}
|
|
1193
|
+
static VALUE iodine_handler_on_auth_rack_sse(VALUE handler, VALUE client) {
|
|
1194
|
+
return iodine_handler_on_auth_rack_internal(handler,
|
|
1195
|
+
client,
|
|
1196
|
+
IODINE_RACK_UPGRADE_SSE_SYM);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
static VALUE iodine_handler_method_injection__inner(VALUE self,
|
|
1200
|
+
VALUE handler,
|
|
1201
|
+
bool is_middleware) {
|
|
1202
|
+
static VALUE previous = Qnil;
|
|
1203
|
+
if (handler == previous)
|
|
1204
|
+
return handler;
|
|
1205
|
+
previous = handler;
|
|
1206
|
+
/* test for handler responses and set a callback if missing */
|
|
1207
|
+
#define IODINE_DEFINE_MISSING_CALLBACK(id) \
|
|
1208
|
+
do { \
|
|
1209
|
+
if (!rb_respond_to(handler, id)) \
|
|
1210
|
+
rb_define_singleton_method(handler, \
|
|
1211
|
+
rb_id2name(id), \
|
|
1212
|
+
iodine_handler_default_on_event, \
|
|
1213
|
+
1); \
|
|
1214
|
+
} while (0)
|
|
1215
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_FINISH_ID);
|
|
1216
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_CLOSE_ID);
|
|
1217
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_EVENTSOURCE_ID);
|
|
1218
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_EVENTSOURCE_RECONNECT_ID);
|
|
1219
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_DRAINED_ID);
|
|
1220
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_SHUTDOWN_ID);
|
|
1221
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_TIMEOUT_ID);
|
|
1222
|
+
#undef IODINE_DEFINE_MISSING_CALLBACK
|
|
1223
|
+
|
|
1224
|
+
#define IODINE_DEFINE_MISSING_CALLBACK(id, callback, nargs) \
|
|
1225
|
+
do { \
|
|
1226
|
+
if (!rb_respond_to(handler, id)) \
|
|
1227
|
+
rb_define_singleton_method(handler, rb_id2name(id), callback, nargs); \
|
|
1228
|
+
} while (0)
|
|
1229
|
+
|
|
1230
|
+
/* Has `call` but not `on_http`? assume Rack. */
|
|
1231
|
+
if (rb_respond_to(handler, IODINE_CALL_ID) &&
|
|
1232
|
+
!rb_respond_to(handler, IODINE_ON_HTTP_ID)) {
|
|
1233
|
+
rb_define_singleton_method(handler,
|
|
1234
|
+
"on_http",
|
|
1235
|
+
iodine_handler_default_on_http,
|
|
1236
|
+
1);
|
|
1237
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_SSE_ID,
|
|
1238
|
+
iodine_handler_on_auth_rack_sse,
|
|
1239
|
+
1);
|
|
1240
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID,
|
|
1241
|
+
iodine_handler_on_auth_rack_ws,
|
|
1242
|
+
1);
|
|
1243
|
+
} else {
|
|
1244
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_HTTP_ID,
|
|
1245
|
+
iodine_handler_default_on_http404,
|
|
1246
|
+
1);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (rb_respond_to(handler, IODINE_ON_AUTHENTICATE_ID)) {
|
|
1250
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_SSE_ID,
|
|
1251
|
+
iodine_handler_on_auth_reroute,
|
|
1252
|
+
1);
|
|
1253
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID,
|
|
1254
|
+
iodine_handler_on_auth_reroute,
|
|
1255
|
+
1);
|
|
1256
|
+
} else {
|
|
1257
|
+
_Bool responds = rb_respond_to(handler, IODINE_ON_OPEN_ID);
|
|
1258
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_SSE_ID,
|
|
1259
|
+
(responds ? iodine_handler_default_on_auth
|
|
1260
|
+
: iodine_handler_default_on_authx),
|
|
1261
|
+
1);
|
|
1262
|
+
responds |= rb_respond_to(handler, IODINE_ON_MESSAGE_ID);
|
|
1263
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_AUTHENTICATE_WEBSOCKET_ID,
|
|
1264
|
+
(responds ? iodine_handler_default_on_auth
|
|
1265
|
+
: iodine_handler_default_on_authx),
|
|
1266
|
+
1);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/* should be defined last, as its existence controls behavior */
|
|
1270
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_OPEN_ID,
|
|
1271
|
+
iodine_handler_default_on_event,
|
|
1272
|
+
1);
|
|
1273
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_ON_MESSAGE_ID,
|
|
1274
|
+
iodine_handler_default_on_message,
|
|
1275
|
+
2);
|
|
1276
|
+
#undef IODINE_DEFINE_MISSING_CALLBACK
|
|
1277
|
+
return handler;
|
|
1278
|
+
(void)self;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
static VALUE iodine_handler_method_injection(VALUE self, VALUE handler) {
|
|
1282
|
+
return iodine_handler_method_injection__inner(self, handler, 0);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
static VALUE iodine_resource_handler_method_injection(int argc,
|
|
1286
|
+
VALUE *argv,
|
|
1287
|
+
VALUE self) {
|
|
1288
|
+
for (int i = 0; i < argc; ++i) {
|
|
1289
|
+
VALUE handler = argv[i];
|
|
1290
|
+
if (rb_respond_to(handler, IODINE_ON_HTTP_ID)) {
|
|
1291
|
+
rb_raise(
|
|
1292
|
+
rb_eRuntimeError,
|
|
1293
|
+
"Object already has an `on_http` callback - can't be made into a "
|
|
1294
|
+
"resource app.");
|
|
1295
|
+
}
|
|
1296
|
+
rb_define_singleton_method(handler,
|
|
1297
|
+
rb_id2name(IODINE_ON_HTTP_ID),
|
|
1298
|
+
iodine_handler_default_on_http_rest,
|
|
1299
|
+
1);
|
|
1300
|
+
#define IODINE_DEFINE_MISSING_CALLBACK(id) \
|
|
1301
|
+
do { \
|
|
1302
|
+
if (!rb_respond_to(handler, id)) \
|
|
1303
|
+
rb_define_singleton_method(handler, \
|
|
1304
|
+
rb_id2name(id), \
|
|
1305
|
+
iodine_handler_default_on_http405, \
|
|
1306
|
+
1); \
|
|
1307
|
+
} while (0)
|
|
1308
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_INDEX_ID);
|
|
1309
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_SHOW_ID);
|
|
1310
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_NEW_ID);
|
|
1311
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_EDIT_ID);
|
|
1312
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_CREATE_ID);
|
|
1313
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_UPDATE_ID);
|
|
1314
|
+
IODINE_DEFINE_MISSING_CALLBACK(IODINE_DELETE_ID);
|
|
1315
|
+
if (!rb_respond_to(handler, IODINE_QUERY_ID))
|
|
1316
|
+
rb_define_singleton_method(handler,
|
|
1317
|
+
rb_id2name(IODINE_QUERY_ID),
|
|
1318
|
+
iodine_handler_default_on_http_query,
|
|
1319
|
+
1);
|
|
1320
|
+
#undef IODINE_DEFINE_MISSING_CALLBACK
|
|
1321
|
+
iodine_handler_method_injection__inner(self, handler, 0); /* and the rest */
|
|
1322
|
+
}
|
|
1323
|
+
return (argc ? argv[0] : Qnil);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/* *****************************************************************************
|
|
1327
|
+
Handler Set function
|
|
1328
|
+
***************************************************************************** */
|
|
1329
|
+
|
|
1330
|
+
/** Sets the client's handler object, adding default callback methods if
|
|
1331
|
+
* missing. */
|
|
1332
|
+
static VALUE iodine_connection_handler_set(VALUE client, VALUE handler) {
|
|
1333
|
+
iodine_connection_s *c = iodine_connection_ptr(client);
|
|
1334
|
+
if (!c)
|
|
1335
|
+
return Qnil;
|
|
1336
|
+
if (IODINE_STORE_IS_SKIP(handler))
|
|
1337
|
+
rb_raise(rb_eArgError, "invalid handler!");
|
|
1338
|
+
iodine_handler_method_injection(Qnil, handler);
|
|
1339
|
+
c->store[IODINE_CONNECTION_STORE_handler] = handler;
|
|
1340
|
+
return handler;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/* *****************************************************************************
|
|
1344
|
+
ENV get / conversion
|
|
1345
|
+
***************************************************************************** */
|
|
1346
|
+
|
|
1347
|
+
static int iodine_env_populate_header_data(fio_http_s *h,
|
|
1348
|
+
fio_str_info_s n,
|
|
1349
|
+
fio_str_info_s v,
|
|
1350
|
+
void *c_) {
|
|
1351
|
+
iodine_connection_s *c = (iodine_connection_s *)c_;
|
|
1352
|
+
VALUE key = STORE.header_name(n);
|
|
1353
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = key;
|
|
1354
|
+
/* copy value */
|
|
1355
|
+
VALUE val = rb_str_new(v.buf, v.len);
|
|
1356
|
+
c->store[IODINE_CONNECTION_STORE_rack] = val; /* unused at this point */
|
|
1357
|
+
/* finish up */
|
|
1358
|
+
rb_hash_aset(c->store[IODINE_CONNECTION_STORE_env], key, val);
|
|
1359
|
+
c->store[IODINE_CONNECTION_STORE_rack] = Qnil;
|
|
1360
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1361
|
+
return 0;
|
|
1362
|
+
(void)h;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
static void iodine_env_set_key_pair(VALUE env,
|
|
1366
|
+
fio_str_info_s n,
|
|
1367
|
+
fio_str_info_s v) {
|
|
1368
|
+
VALUE key = STORE.frozen_str(n);
|
|
1369
|
+
STORE.cache(key);
|
|
1370
|
+
VALUE val = rb_str_new(v.buf, v.len);
|
|
1371
|
+
STORE.cache(val);
|
|
1372
|
+
rb_hash_aset(env, key, val);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
static void iodine_env_set_key_pair_const(VALUE env,
|
|
1376
|
+
fio_str_info_s n,
|
|
1377
|
+
fio_str_info_s v) {
|
|
1378
|
+
VALUE key = STORE.frozen_str(n);
|
|
1379
|
+
STORE.cache(key);
|
|
1380
|
+
VALUE val = STORE.frozen_str(v);
|
|
1381
|
+
STORE.cache(val);
|
|
1382
|
+
rb_hash_aset(env, key, val);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
static void iodine_env_set_const_val(VALUE env,
|
|
1386
|
+
fio_str_info_s n,
|
|
1387
|
+
VALUE val,
|
|
1388
|
+
VALUE *keeper) {
|
|
1389
|
+
VALUE key = STORE.frozen_str(n);
|
|
1390
|
+
*keeper = key;
|
|
1391
|
+
rb_hash_aset(env, key, val);
|
|
1392
|
+
*keeper = Qnil;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
static void iodine_env_set_const_key(VALUE env,
|
|
1396
|
+
VALUE key,
|
|
1397
|
+
fio_str_info_s v,
|
|
1398
|
+
VALUE *keeper) {
|
|
1399
|
+
VALUE val = STORE.frozen_str(v);
|
|
1400
|
+
*keeper = val;
|
|
1401
|
+
rb_hash_aset(env, key, val);
|
|
1402
|
+
*keeper = Qnil;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
static void iodine_connection_init_env_template(fio_buf_info_s at_url) {
|
|
1406
|
+
VALUE env = IODINE_CONNECTION_ENV_TEMPLATE = rb_hash_new();
|
|
1407
|
+
VALUE keeper = Qnil;
|
|
1408
|
+
STORE.hold(IODINE_CONNECTION_ENV_TEMPLATE);
|
|
1409
|
+
/* set template, see https://github.com/rack/rack/blob/main/SPEC.rdoc */
|
|
1410
|
+
iodine_env_set_const_val(env,
|
|
1411
|
+
FIO_STR_INFO1((char *)"rack.multithread"),
|
|
1412
|
+
(fio_cli_get_i("-t") ? Qtrue : Qfalse),
|
|
1413
|
+
&keeper);
|
|
1414
|
+
iodine_env_set_const_val(env,
|
|
1415
|
+
FIO_STR_INFO1((char *)"rack.multiprocess"),
|
|
1416
|
+
(fio_cli_get_i("-w") ? Qtrue : Qfalse),
|
|
1417
|
+
&keeper);
|
|
1418
|
+
iodine_env_set_const_val(env,
|
|
1419
|
+
FIO_STR_INFO1((char *)"rack.run_once"),
|
|
1420
|
+
Qfalse,
|
|
1421
|
+
&keeper);
|
|
1422
|
+
iodine_env_set_const_val(env,
|
|
1423
|
+
FIO_STR_INFO1((char *)"rack.errors"),
|
|
1424
|
+
rb_stderr,
|
|
1425
|
+
&keeper);
|
|
1426
|
+
iodine_env_set_const_val(env,
|
|
1427
|
+
FIO_STR_INFO1((char *)"rack.multipart.buffer_size"),
|
|
1428
|
+
Qnil,
|
|
1429
|
+
&keeper);
|
|
1430
|
+
iodine_env_set_const_val(
|
|
1431
|
+
env,
|
|
1432
|
+
FIO_STR_INFO1((char *)"rack.multipart.tempfile_factory"),
|
|
1433
|
+
Qnil,
|
|
1434
|
+
&keeper);
|
|
1435
|
+
/* Iodine::Logger meets the Rack logger interface and shares the C STL log
|
|
1436
|
+
* sink and level (no dependency on the `logger` gem). */
|
|
1437
|
+
iodine_env_set_const_val(env,
|
|
1438
|
+
FIO_STR_INFO1((char *)"rack.logger"),
|
|
1439
|
+
iodine_rb_IODINE_LOGGER,
|
|
1440
|
+
&keeper);
|
|
1441
|
+
iodine_env_set_key_pair(env,
|
|
1442
|
+
FIO_STR_INFO2((char *)"rack.url_scheme", 15),
|
|
1443
|
+
FIO_STR_INFO2((char *)"https", 4));
|
|
1444
|
+
|
|
1445
|
+
rb_hash_aset(env, IODINE_RACK_AFTER_RPLY_STR, Qnil);
|
|
1446
|
+
|
|
1447
|
+
iodine_env_set_const_val(
|
|
1448
|
+
env,
|
|
1449
|
+
FIO_STR_INFO1((char *)"rack.input"),
|
|
1450
|
+
rb_funcallv(rb_const_get(rb_cObject, rb_intern2("StringIO", 8)),
|
|
1451
|
+
IODINE_NEW_ID,
|
|
1452
|
+
0,
|
|
1453
|
+
NULL),
|
|
1454
|
+
&keeper);
|
|
1455
|
+
/* FIXME! TODO! */
|
|
1456
|
+
iodine_env_set_const_val(env,
|
|
1457
|
+
FIO_STR_INFO1((char *)"rack.session"),
|
|
1458
|
+
Qnil,
|
|
1459
|
+
&keeper);
|
|
1460
|
+
{
|
|
1461
|
+
VALUE ver = rb_ary_new();
|
|
1462
|
+
iodine_env_set_const_val(env,
|
|
1463
|
+
FIO_STR_INFO1((char *)"rack.version"),
|
|
1464
|
+
ver,
|
|
1465
|
+
&keeper);
|
|
1466
|
+
rb_ary_push(ver, INT2NUM(1));
|
|
1467
|
+
rb_ary_push(ver, INT2NUM(3));
|
|
1468
|
+
}
|
|
1469
|
+
iodine_env_set_const_val(env,
|
|
1470
|
+
FIO_STR_INFO1((char *)"rack.hijack?"),
|
|
1471
|
+
Qtrue,
|
|
1472
|
+
&keeper);
|
|
1473
|
+
rb_hash_aset(env, IODINE_RACK_HIJACK_STR, Qnil);
|
|
1474
|
+
rb_hash_aset(env, IODINE_RACK_NEORACK_CLIENT, Qnil);
|
|
1475
|
+
rb_hash_aset(env, IODINE_RACK_REQUEST_METHOD, Qtrue);
|
|
1476
|
+
rb_hash_aset(env, IODINE_RACK_PATH_INFO, Qtrue);
|
|
1477
|
+
rb_hash_aset(env, IODINE_RACK_QUERY_STRING, Qtrue);
|
|
1478
|
+
rb_hash_aset(env, IODINE_RACK_REMOTE_ADDR, Qtrue);
|
|
1479
|
+
rb_hash_aset(env, IODINE_RACK_SERVER_PROTOCOL, Qtrue);
|
|
1480
|
+
rb_hash_aset(env, IODINE_RACK_HTTP_VERSION, Qtrue);
|
|
1481
|
+
rb_hash_aset(env, IODINE_RACK_SERVER_NAME, rb_str_new("", 0));
|
|
1482
|
+
rb_hash_aset(env, IODINE_RACK_SCRIPT_NAME, rb_str_new("", 0));
|
|
1483
|
+
|
|
1484
|
+
{ /* SERVER_PORT must be a String Object. */
|
|
1485
|
+
uint64_t port_num = 3000;
|
|
1486
|
+
uint8_t was_set = 0;
|
|
1487
|
+
if (at_url.len) {
|
|
1488
|
+
fio_url_s u = fio_url_parse(at_url.buf, at_url.len);
|
|
1489
|
+
port_num = u.port.len ? fio_atol(&u.port.buf) : 0;
|
|
1490
|
+
} else {
|
|
1491
|
+
if (fio_cli_get_i("-p")) {
|
|
1492
|
+
port_num = fio_cli_get_i("-p");
|
|
1493
|
+
was_set = 1;
|
|
1494
|
+
} else if (fio_cli_get("-b")) {
|
|
1495
|
+
fio_url_s u =
|
|
1496
|
+
fio_url_parse(fio_cli_get("-b"), strlen(fio_cli_get("-b")));
|
|
1497
|
+
if (u.port.len) {
|
|
1498
|
+
port_num = fio_atol(&u.port.buf);
|
|
1499
|
+
was_set = 1;
|
|
1500
|
+
} else if (u.host.len) {
|
|
1501
|
+
port_num = 80;
|
|
1502
|
+
if (fio_url_is_tls(u).tls)
|
|
1503
|
+
port_num = 443;
|
|
1504
|
+
was_set = 1;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (!was_set && getenv("PORT") && strlen(getenv("PORT"))) {
|
|
1508
|
+
char *tmp = getenv("PORT");
|
|
1509
|
+
port_num = fio_atol(&tmp);
|
|
1510
|
+
was_set = 1;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
FIO_STR_INFO_TMP_VAR(prt, 32);
|
|
1514
|
+
fio_string_write_u(&prt, NULL, port_num);
|
|
1515
|
+
iodine_env_set_key_pair_const(env,
|
|
1516
|
+
FIO_STR_INFO1((char *)"SERVER_PORT"),
|
|
1517
|
+
prt);
|
|
1518
|
+
}
|
|
1519
|
+
/* initialize the HTTPS template */
|
|
1520
|
+
IODINE_CONNECTION_ENV_TEMPLATE_HTTPS = rb_hash_dup(env);
|
|
1521
|
+
STORE.hold(IODINE_CONNECTION_ENV_TEMPLATE_HTTPS);
|
|
1522
|
+
iodine_env_set_key_pair(IODINE_CONNECTION_ENV_TEMPLATE_HTTPS,
|
|
1523
|
+
FIO_STR_INFO2((char *)"rack.url_scheme", 15),
|
|
1524
|
+
FIO_STR_INFO2((char *)"https", 5));
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/** Returns the client's current `env` object. */
|
|
1528
|
+
static VALUE iodine_connection_env_get(VALUE self) {
|
|
1529
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
1530
|
+
if (!c)
|
|
1531
|
+
return Qnil;
|
|
1532
|
+
if (c->store[IODINE_CONNECTION_STORE_env] != Qnil)
|
|
1533
|
+
return c->store[IODINE_CONNECTION_STORE_env];
|
|
1534
|
+
if (!c->http)
|
|
1535
|
+
return (c->store[IODINE_CONNECTION_STORE_env] = rb_hash_new());
|
|
1536
|
+
VALUE env = c->store[IODINE_CONNECTION_STORE_env] = rb_hash_dup(
|
|
1537
|
+
fio_io_tls(fio_http_io(c->http)) ? IODINE_CONNECTION_ENV_TEMPLATE_HTTPS
|
|
1538
|
+
: IODINE_CONNECTION_ENV_TEMPLATE);
|
|
1539
|
+
|
|
1540
|
+
rb_hash_aset(env, IODINE_RACK_NEORACK_CLIENT, self);
|
|
1541
|
+
{
|
|
1542
|
+
VALUE tmp = rb_obj_method(self, IODINE_RACK_HIJACK_ID_SYM);
|
|
1543
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = tmp;
|
|
1544
|
+
rb_hash_aset(env, IODINE_RACK_HIJACK_STR, tmp);
|
|
1545
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/* populate env, see https://github.com/rack/rack/blob/main/SPEC.rdoc */
|
|
1549
|
+
fio_http_request_header_each(c->http,
|
|
1550
|
+
iodine_env_populate_header_data,
|
|
1551
|
+
(void *)c);
|
|
1552
|
+
|
|
1553
|
+
if (fio_http_body_length(c->http)) {
|
|
1554
|
+
FIO_STR_INFO_TMP_VAR(num2str, 512);
|
|
1555
|
+
fio_string_write_u(&num2str, NULL, fio_http_body_length(c->http));
|
|
1556
|
+
VALUE clen_str = rb_str_new(num2str.buf, num2str.len);
|
|
1557
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = clen_str;
|
|
1558
|
+
rb_hash_aset(env, IODINE_RACK_CONTENT_LENGTH, clen_str);
|
|
1559
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1560
|
+
rb_hash_aset(env, IODINE_RACK_INPUT, self);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
iodine_env_set_const_key(env,
|
|
1564
|
+
IODINE_RACK_REQUEST_METHOD,
|
|
1565
|
+
fio_keystr_info(&c->http->method),
|
|
1566
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1567
|
+
iodine_env_set_const_key(env,
|
|
1568
|
+
IODINE_RACK_PATH_INFO,
|
|
1569
|
+
fio_keystr_info(&c->http->path),
|
|
1570
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1571
|
+
iodine_env_set_const_key(env,
|
|
1572
|
+
IODINE_RACK_QUERY_STRING,
|
|
1573
|
+
fio_keystr_info(&c->http->query),
|
|
1574
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1575
|
+
{ /* Router: SCRIPT_NAME*/
|
|
1576
|
+
fio_str_info_s opath = fio_http_opath(c->http);
|
|
1577
|
+
fio_str_info_s path = fio_http_path(c->http);
|
|
1578
|
+
path.len -= (path.len == 1);
|
|
1579
|
+
opath.len -= path.len;
|
|
1580
|
+
if (opath.len)
|
|
1581
|
+
iodine_env_set_const_key(env,
|
|
1582
|
+
IODINE_RACK_SCRIPT_NAME,
|
|
1583
|
+
opath,
|
|
1584
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1585
|
+
}
|
|
1586
|
+
{
|
|
1587
|
+
fio_str_info_s host =
|
|
1588
|
+
fio_http_request_header(c->http, FIO_STR_INFO2((char *)"host", 4), 0);
|
|
1589
|
+
fio_url_s u = fio_url_parse(host.buf, host.len);
|
|
1590
|
+
iodine_env_set_const_key(env,
|
|
1591
|
+
IODINE_RACK_SERVER_NAME,
|
|
1592
|
+
FIO_BUF2STR_INFO(u.host),
|
|
1593
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1594
|
+
}
|
|
1595
|
+
iodine_env_set_const_key(env,
|
|
1596
|
+
IODINE_RACK_SERVER_PROTOCOL,
|
|
1597
|
+
fio_keystr_info(&c->http->version),
|
|
1598
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1599
|
+
iodine_env_set_const_key(env,
|
|
1600
|
+
IODINE_RACK_HTTP_VERSION,
|
|
1601
|
+
fio_keystr_info(&c->http->version),
|
|
1602
|
+
(c->store + IODINE_CONNECTION_STORE_tmp));
|
|
1603
|
+
{
|
|
1604
|
+
VALUE addr = iodine_connection_peer_addr(self);
|
|
1605
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = addr;
|
|
1606
|
+
rb_hash_aset(env, IODINE_RACK_REMOTE_ADDR, addr);
|
|
1607
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1608
|
+
}
|
|
1609
|
+
{
|
|
1610
|
+
VALUE ary_after = rb_ary_new();
|
|
1611
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = ary_after;
|
|
1612
|
+
rb_hash_aset(env, IODINE_RACK_AFTER_RPLY_STR, ary_after);
|
|
1613
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
return env;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/* *****************************************************************************
|
|
1620
|
+
Raw IO Callbacks and Helpers (TCP/IP)
|
|
1621
|
+
***************************************************************************** */
|
|
1622
|
+
|
|
1623
|
+
typedef struct {
|
|
1624
|
+
fio_io_s *io;
|
|
1625
|
+
size_t len;
|
|
1626
|
+
char buf[IODINE_RAW_ON_DATA_READ_BUFFER];
|
|
1627
|
+
} iodine_io_raw_on_data_info_s;
|
|
1628
|
+
|
|
1629
|
+
static void *iodine_io_raw_on_attach_in_GIL(void *io) {
|
|
1630
|
+
VALUE connection = iodine_connection_create_from_io(io);
|
|
1631
|
+
if (!connection || connection == Qnil)
|
|
1632
|
+
return NULL;
|
|
1633
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1634
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1635
|
+
IODINE_ON_OPEN_ID,
|
|
1636
|
+
1,
|
|
1637
|
+
&connection);
|
|
1638
|
+
return NULL;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/** Called when an IO is attached to the protocol. */
|
|
1642
|
+
static void iodine_io_raw_on_attach(fio_io_s *io) {
|
|
1643
|
+
iodine_c_call_with(iodine_io_raw_on_attach_in_GIL, io);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/** Tests whether a nonblocking client socket completed its connection. */
|
|
1647
|
+
static int iodine_io_raw_client_connect_failed(fio_io_s *io) {
|
|
1648
|
+
int error = 0;
|
|
1649
|
+
#if FIO_OS_WIN
|
|
1650
|
+
int error_len = sizeof(error);
|
|
1651
|
+
return fio___winsock_fn.getsockopt(fio_io_fd(io),
|
|
1652
|
+
SOL_SOCKET,
|
|
1653
|
+
SO_ERROR,
|
|
1654
|
+
(char *)&error,
|
|
1655
|
+
&error_len) ||
|
|
1656
|
+
error;
|
|
1657
|
+
#else
|
|
1658
|
+
socklen_t error_len = sizeof(error);
|
|
1659
|
+
return getsockopt(fio_io_fd(io), SOL_SOCKET, SO_ERROR, &error, &error_len) ||
|
|
1660
|
+
error;
|
|
1661
|
+
#endif
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
typedef struct {
|
|
1665
|
+
fio_io_s *io;
|
|
1666
|
+
int should_close;
|
|
1667
|
+
} iodine_io_raw_client_attach_args_s;
|
|
1668
|
+
|
|
1669
|
+
/** Attaches an outgoing raw TCP client to its pre-allocated Ruby wrapper. */
|
|
1670
|
+
static void *iodine_io_raw_client_on_attach_in_gvl(void *args_) {
|
|
1671
|
+
iodine_io_raw_client_attach_args_s *args =
|
|
1672
|
+
(iodine_io_raw_client_attach_args_s *)args_;
|
|
1673
|
+
VALUE connection = (VALUE)fio_io_udata(args->io);
|
|
1674
|
+
iodine_connection_s *c = NULL;
|
|
1675
|
+
/* A missing wrapper is unrecoverable - close rather than leak a live IO
|
|
1676
|
+
* whose every callback would bail out on the NULL udata. */
|
|
1677
|
+
if (!connection || connection == Qnil ||
|
|
1678
|
+
!(c = iodine_connection_ptr(connection))) {
|
|
1679
|
+
args->should_close = 1;
|
|
1680
|
+
return NULL;
|
|
1681
|
+
}
|
|
1682
|
+
c->io = args->io;
|
|
1683
|
+
args->should_close = iodine_io_raw_client_connect_failed(args->io);
|
|
1684
|
+
if (args->should_close)
|
|
1685
|
+
return NULL;
|
|
1686
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1687
|
+
IODINE_ON_OPEN_ID,
|
|
1688
|
+
1,
|
|
1689
|
+
&connection);
|
|
1690
|
+
return NULL;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
static void *iodine_io_raw_client_close_without_gvl(void *io_) {
|
|
1694
|
+
fio_io_close((fio_io_s *)io_);
|
|
1695
|
+
return NULL;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
static void iodine_io_raw_client_on_attach(fio_io_s *io) {
|
|
1699
|
+
iodine_io_raw_client_attach_args_s args = {.io = io};
|
|
1700
|
+
iodine_c_call_with(iodine_io_raw_client_on_attach_in_gvl, &args);
|
|
1701
|
+
if (args.should_close)
|
|
1702
|
+
iodine_c_call_without(iodine_io_raw_client_close_without_gvl, io);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
static void *iodine_io_raw_on_data_in_GVL(void *info_) {
|
|
1706
|
+
iodine_io_raw_on_data_info_s *i = (iodine_io_raw_on_data_info_s *)info_;
|
|
1707
|
+
VALUE connection = (VALUE)fio_io_udata(i->io);
|
|
1708
|
+
if (!connection || connection == Qnil)
|
|
1709
|
+
return NULL;
|
|
1710
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1711
|
+
VALUE buf = rb_enc_str_new(i->buf, (long)i->len, IodineBinaryEncoding);
|
|
1712
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = buf;
|
|
1713
|
+
VALUE args[] = {connection, buf};
|
|
1714
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1715
|
+
IODINE_ON_MESSAGE_ID,
|
|
1716
|
+
2,
|
|
1717
|
+
args);
|
|
1718
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1719
|
+
return NULL;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
static void iodine_io_raw_on_data(fio_io_s *io) {
|
|
1723
|
+
iodine_io_raw_on_data_info_s i;
|
|
1724
|
+
i.io = io;
|
|
1725
|
+
i.len = fio_io_read(io, i.buf, IODINE_RAW_ON_DATA_READ_BUFFER);
|
|
1726
|
+
if (!i.len)
|
|
1727
|
+
return;
|
|
1728
|
+
iodine_c_call_with(iodine_io_raw_on_data_in_GVL, &i);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
#define IODINE_CONNECTION_DEF_CB(named, id) \
|
|
1732
|
+
static void iodine_io_raw_##named(fio_io_s *io) { \
|
|
1733
|
+
VALUE connection = (VALUE)fio_io_udata(io); \
|
|
1734
|
+
if (!connection || connection == Qnil) \
|
|
1735
|
+
return; \
|
|
1736
|
+
iodine_connection_s *c = iodine_connection_ptr(connection); \
|
|
1737
|
+
if (!c) \
|
|
1738
|
+
return; \
|
|
1739
|
+
iodine_ruby_call_anywhere(c->store[IODINE_CONNECTION_STORE_handler], \
|
|
1740
|
+
id, \
|
|
1741
|
+
1, \
|
|
1742
|
+
&connection); \
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/** called once all pending `write` calls are finished. */
|
|
1746
|
+
IODINE_CONNECTION_DEF_CB(on_ready, IODINE_ON_DRAINED_ID);
|
|
1747
|
+
/** called if the connection is open when the server is shutting down. */
|
|
1748
|
+
IODINE_CONNECTION_DEF_CB(on_shutdown, IODINE_ON_SHUTDOWN_ID);
|
|
1749
|
+
/** Called when a connection's open while server is shutting down. */
|
|
1750
|
+
static void iodine_io_raw_on_shutdown(fio_io_s *io);
|
|
1751
|
+
/** Called when a connection's timeout was reached */
|
|
1752
|
+
IODINE_CONNECTION_DEF_CB(on_timeout, IODINE_ON_TIMEOUT_ID);
|
|
1753
|
+
|
|
1754
|
+
#undef IODINE_CONNECTION_DEF_CB
|
|
1755
|
+
|
|
1756
|
+
/** Called after the connection was closed (called once per IO). */
|
|
1757
|
+
static void iodine_io_raw_on_close(void *buf, void *udata) {
|
|
1758
|
+
VALUE connection = (VALUE)udata;
|
|
1759
|
+
if (!connection || connection == Qnil)
|
|
1760
|
+
return;
|
|
1761
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1762
|
+
if (!c)
|
|
1763
|
+
return;
|
|
1764
|
+
iodine_ruby_call_anywhere(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1765
|
+
IODINE_ON_CLOSE_ID,
|
|
1766
|
+
1,
|
|
1767
|
+
&connection);
|
|
1768
|
+
(void)buf;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
static fio_io_protocol_s IODINE_RAW_PROTOCOL = {
|
|
1772
|
+
.on_attach = iodine_io_raw_on_attach,
|
|
1773
|
+
.on_data = iodine_io_raw_on_data,
|
|
1774
|
+
.on_ready = iodine_io_raw_on_ready,
|
|
1775
|
+
.on_timeout = iodine_io_raw_on_timeout,
|
|
1776
|
+
.on_shutdown = iodine_io_raw_on_shutdown,
|
|
1777
|
+
.on_close = iodine_io_raw_on_close,
|
|
1778
|
+
.on_pubsub = FIO_ON_MESSAGE_SEND_MESSAGE,
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
/* *****************************************************************************
|
|
1782
|
+
HTTP / WebSockets Callbacks and Helpers
|
|
1783
|
+
***************************************************************************** */
|
|
1784
|
+
|
|
1785
|
+
static void *iodine_io_http_on_http_internal(void *h_) {
|
|
1786
|
+
fio_http_s *h = (fio_http_s *)h_;
|
|
1787
|
+
VALUE connection = iodine_connection_create_from_http(h);
|
|
1788
|
+
if (FIO_UNLIKELY(!connection || connection == Qnil)) {
|
|
1789
|
+
FIO_LOG_FATAL("`on_http` couldn't allocate Iodine::Connection object!");
|
|
1790
|
+
return NULL;
|
|
1791
|
+
}
|
|
1792
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1793
|
+
iodine_caller_result_s e =
|
|
1794
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1795
|
+
IODINE_ON_HTTP_ID,
|
|
1796
|
+
1,
|
|
1797
|
+
&connection);
|
|
1798
|
+
if (e.exception) {
|
|
1799
|
+
fio_http_send_error_response(h, 500);
|
|
1800
|
+
}
|
|
1801
|
+
return NULL;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
static void iodine_io_http_on_http(fio_http_s *h) {
|
|
1805
|
+
VALUE handler = (VALUE)fio_http_udata(h);
|
|
1806
|
+
if (FIO_UNLIKELY(!handler || handler == Qnil)) {
|
|
1807
|
+
FIO_LOG_FATAL("`on_http` callback couldn't find handler!");
|
|
1808
|
+
fio_http_send_error_response(h, 500);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
iodine_c_call_with(iodine_io_http_on_http_internal, (void *)h);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
#define IODINE_CONNECTION_DEF_CB(named, id, free_handle) \
|
|
1815
|
+
static void iodine_io_http_##named(fio_http_s *h) { \
|
|
1816
|
+
VALUE connection = (VALUE)fio_http_udata2(h); \
|
|
1817
|
+
if (!connection || connection == Qnil) \
|
|
1818
|
+
return; \
|
|
1819
|
+
iodine_connection_s *c = iodine_connection_ptr(connection); \
|
|
1820
|
+
if (!c) \
|
|
1821
|
+
return; \
|
|
1822
|
+
iodine_ruby_call_anywhere(c->store[IODINE_CONNECTION_STORE_handler], \
|
|
1823
|
+
id, \
|
|
1824
|
+
1, \
|
|
1825
|
+
&connection); \
|
|
1826
|
+
if (free_handle) { \
|
|
1827
|
+
iodine_ruby_call_anywhere(c->store[IODINE_CONNECTION_STORE_handler], \
|
|
1828
|
+
IODINE_ON_FINISH_ID, \
|
|
1829
|
+
1, \
|
|
1830
|
+
&connection); \
|
|
1831
|
+
fio_http_free(c->http); \
|
|
1832
|
+
c->http = NULL; \
|
|
1833
|
+
c->io = NULL; \
|
|
1834
|
+
STORE.release(connection); \
|
|
1835
|
+
} \
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/** Called when an IO is attached to the protocol. */
|
|
1839
|
+
IODINE_CONNECTION_DEF_CB(on_open, IODINE_ON_OPEN_ID, 0);
|
|
1840
|
+
/** called once all pending `write` calls are finished. */
|
|
1841
|
+
IODINE_CONNECTION_DEF_CB(on_ready, IODINE_ON_DRAINED_ID, 0);
|
|
1842
|
+
/** called if the connection is open when the server is shutting down. */
|
|
1843
|
+
IODINE_CONNECTION_DEF_CB(on_shutdown, IODINE_ON_SHUTDOWN_ID, 0);
|
|
1844
|
+
/** Called after the connection was closed (called once per IO). */
|
|
1845
|
+
IODINE_CONNECTION_DEF_CB(on_close, IODINE_ON_CLOSE_ID, 1);
|
|
1846
|
+
|
|
1847
|
+
#undef IODINE_CONNECTION_DEF_CB
|
|
1848
|
+
#define IODINE_CONNECTION_DEF_CB(named, id, flag) \
|
|
1849
|
+
static void *iodine_io_http_##named##_internal(void *h_) { \
|
|
1850
|
+
fio_http_s *h = (fio_http_s *)h_; \
|
|
1851
|
+
VALUE connection = iodine_connection_create_from_http(h); \
|
|
1852
|
+
if (!connection || connection == Qnil) \
|
|
1853
|
+
return (void *)-1; \
|
|
1854
|
+
iodine_connection_s *c = iodine_connection_ptr(connection); \
|
|
1855
|
+
if (!c) \
|
|
1856
|
+
return (void *)-1; \
|
|
1857
|
+
c->flags |= flag; \
|
|
1858
|
+
iodine_caller_result_s r = \
|
|
1859
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], \
|
|
1860
|
+
id, \
|
|
1861
|
+
1, \
|
|
1862
|
+
&connection); \
|
|
1863
|
+
if (!r.exception && r.result == Qtrue) \
|
|
1864
|
+
return NULL; \
|
|
1865
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler], \
|
|
1866
|
+
IODINE_ON_FINISH_ID, \
|
|
1867
|
+
1, \
|
|
1868
|
+
&connection); \
|
|
1869
|
+
fio_http_free(c->http); \
|
|
1870
|
+
c->http = NULL; \
|
|
1871
|
+
c->io = NULL; \
|
|
1872
|
+
return (void *)-1; \
|
|
1873
|
+
} \
|
|
1874
|
+
static int iodine_io_http_##named(fio_http_s *h) { \
|
|
1875
|
+
if (!iodine_c_call_with(iodine_io_http_##named##_internal, h)) \
|
|
1876
|
+
return 0; \
|
|
1877
|
+
return -1; \
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/** Called after the connection was closed (called once per IO). */
|
|
1881
|
+
IODINE_CONNECTION_DEF_CB(on_authenticate_sse,
|
|
1882
|
+
IODINE_ON_AUTHENTICATE_SSE_ID,
|
|
1883
|
+
IODINE_CONNECTION_UPGRADE_SSE);
|
|
1884
|
+
/** Called after the connection was closed (called once per IO). */
|
|
1885
|
+
IODINE_CONNECTION_DEF_CB(on_authenticate_websocket,
|
|
1886
|
+
IODINE_ON_AUTHENTICATE_WEBSOCKET_ID,
|
|
1887
|
+
IODINE_CONNECTION_UPGRADE_WS);
|
|
1888
|
+
|
|
1889
|
+
#undef IODINE_CONNECTION_DEF_CB
|
|
1890
|
+
|
|
1891
|
+
typedef struct {
|
|
1892
|
+
fio_http_s *h;
|
|
1893
|
+
fio_buf_info_s id;
|
|
1894
|
+
fio_buf_info_s event;
|
|
1895
|
+
fio_buf_info_s data;
|
|
1896
|
+
} iodine_io_http_on_eventsource_internal_s;
|
|
1897
|
+
|
|
1898
|
+
static void *iodine_io_http_on_eventsource_reconnect_internal(void *info_) {
|
|
1899
|
+
iodine_io_http_on_eventsource_internal_s *i =
|
|
1900
|
+
(iodine_io_http_on_eventsource_internal_s *)info_;
|
|
1901
|
+
VALUE connection = (VALUE)fio_http_udata2(i->h);
|
|
1902
|
+
if (!connection || connection == Qnil)
|
|
1903
|
+
return NULL;
|
|
1904
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1905
|
+
VALUE args[] = {connection, rb_str_new(i->id.buf, i->id.len)};
|
|
1906
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = args[1];
|
|
1907
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1908
|
+
IODINE_ON_EVENTSOURCE_RECONNECT_ID,
|
|
1909
|
+
2,
|
|
1910
|
+
args);
|
|
1911
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1912
|
+
return NULL;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
/** Called when an EventSource reconnect event requests an ID. */
|
|
1916
|
+
static void iodine_io_http_on_eventsource_reconnect(fio_http_s *h,
|
|
1917
|
+
fio_buf_info_s id) {
|
|
1918
|
+
VALUE connection = (VALUE)fio_http_udata2(h);
|
|
1919
|
+
if (!connection || connection == Qnil)
|
|
1920
|
+
return;
|
|
1921
|
+
iodine_io_http_on_eventsource_internal_s info = {.h = h, .id = id};
|
|
1922
|
+
iodine_c_call_with(iodine_io_http_on_eventsource_reconnect_internal, &info);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
static void *iodine_io_http_on_eventsource_internal(void *info_) {
|
|
1926
|
+
iodine_io_http_on_eventsource_internal_s *i =
|
|
1927
|
+
(iodine_io_http_on_eventsource_internal_s *)info_;
|
|
1928
|
+
VALUE connection = (VALUE)fio_http_udata2(i->h);
|
|
1929
|
+
if (!connection || connection == Qnil)
|
|
1930
|
+
return NULL;
|
|
1931
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1932
|
+
fio_pubsub_msg_s msg = {
|
|
1933
|
+
.channel = i->event,
|
|
1934
|
+
.message = i->data,
|
|
1935
|
+
};
|
|
1936
|
+
VALUE args[] = {connection, iodine_pubsub_msg_new(&msg)};
|
|
1937
|
+
iodine_pubsub_msg_id_set(args[1], rb_str_new(i->id.buf, i->id.len));
|
|
1938
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1939
|
+
IODINE_ON_EVENTSOURCE_ID,
|
|
1940
|
+
2,
|
|
1941
|
+
args);
|
|
1942
|
+
STORE.release(args[1]); /* Store.hold(m) called iodine_pubsub_msg_new */
|
|
1943
|
+
return NULL;
|
|
1944
|
+
}
|
|
1945
|
+
/** Called when an EventSource event is received. */
|
|
1946
|
+
static void iodine_io_http_on_eventsource(fio_http_s *h,
|
|
1947
|
+
fio_buf_info_s id,
|
|
1948
|
+
fio_buf_info_s event,
|
|
1949
|
+
fio_buf_info_s data) {
|
|
1950
|
+
iodine_io_http_on_eventsource_internal_s info = {.h = h,
|
|
1951
|
+
.id = id,
|
|
1952
|
+
.event = event,
|
|
1953
|
+
.data = data};
|
|
1954
|
+
iodine_c_call_with(iodine_io_http_on_eventsource_internal, &info);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
typedef struct {
|
|
1958
|
+
fio_http_s *h;
|
|
1959
|
+
fio_buf_info_s msg;
|
|
1960
|
+
uint8_t is_text;
|
|
1961
|
+
} iodine_io_http_on_message_internal_s;
|
|
1962
|
+
|
|
1963
|
+
/** Called when a WebSocket / SSE message arrives. */
|
|
1964
|
+
static void *iodine_io_http_on_message_internal(void *info) {
|
|
1965
|
+
iodine_io_http_on_message_internal_s *i =
|
|
1966
|
+
(iodine_io_http_on_message_internal_s *)info;
|
|
1967
|
+
VALUE connection = (VALUE)fio_http_udata2(i->h);
|
|
1968
|
+
if (!connection || connection == Qnil)
|
|
1969
|
+
return NULL;
|
|
1970
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
1971
|
+
VALUE args[] = {
|
|
1972
|
+
connection,
|
|
1973
|
+
rb_enc_str_new(i->msg.buf,
|
|
1974
|
+
i->msg.len,
|
|
1975
|
+
(i->is_text ? IodineUTF8Encoding : IodineBinaryEncoding))};
|
|
1976
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = args[1];
|
|
1977
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
1978
|
+
IODINE_ON_MESSAGE_ID,
|
|
1979
|
+
2,
|
|
1980
|
+
args);
|
|
1981
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1982
|
+
return NULL;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/** Called when a WebSocket / SSE message arrives. */
|
|
1986
|
+
static void iodine_io_http_on_message(fio_http_s *h,
|
|
1987
|
+
fio_buf_info_s msg,
|
|
1988
|
+
uint8_t is_text) {
|
|
1989
|
+
iodine_io_http_on_message_internal_s info = {
|
|
1990
|
+
.h = h,
|
|
1991
|
+
.msg = msg,
|
|
1992
|
+
.is_text = is_text,
|
|
1993
|
+
};
|
|
1994
|
+
iodine_c_call_with(iodine_io_http_on_message_internal, &info);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
/** Called when a request / response cycle is finished with no Upgrade. */
|
|
1998
|
+
static void iodine_io_http_on_finish(fio_http_s *h) {
|
|
1999
|
+
VALUE connection = (VALUE)fio_http_udata2(h);
|
|
2000
|
+
if (!connection || connection == Qnil)
|
|
2001
|
+
return; /* done in the `on_close` */
|
|
2002
|
+
iodine_connection_s *c = iodine_connection_ptr(connection);
|
|
2003
|
+
if (c->http) {
|
|
2004
|
+
if (c->flags & IODINE_CONNECTION_CLIENT) {
|
|
2005
|
+
iodine_ruby_call_anywhere(c->store[IODINE_CONNECTION_STORE_handler],
|
|
2006
|
+
IODINE_ON_FINISH_ID,
|
|
2007
|
+
1,
|
|
2008
|
+
&connection);
|
|
2009
|
+
} else {
|
|
2010
|
+
iodine_ruby_call_inside(c->store[IODINE_CONNECTION_STORE_handler],
|
|
2011
|
+
IODINE_ON_FINISH_ID,
|
|
2012
|
+
1,
|
|
2013
|
+
&connection);
|
|
2014
|
+
}
|
|
2015
|
+
fio_http_free(h);
|
|
2016
|
+
}
|
|
2017
|
+
c->http = NULL;
|
|
2018
|
+
c->io = NULL;
|
|
2019
|
+
STORE.release(connection);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
/* *****************************************************************************
|
|
2023
|
+
Subscription Helpers
|
|
2024
|
+
***************************************************************************** */
|
|
2025
|
+
|
|
2026
|
+
static void *iodine_connection_on_pubsub_in_gvl(void *m_) {
|
|
2027
|
+
fio_pubsub_msg_s *m = (fio_pubsub_msg_s *)m_;
|
|
2028
|
+
VALUE msg = iodine_pubsub_msg_new(m);
|
|
2029
|
+
/* TODO! move callback to async queue? Is this possible? */
|
|
2030
|
+
iodine_ruby_call_inside((VALUE)m->udata, IODINE_CALL_ID, 1, &msg);
|
|
2031
|
+
STORE.release(msg);
|
|
2032
|
+
return m_;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
static void iodine_connection_on_pubsub(fio_pubsub_msg_s *m) {
|
|
2036
|
+
iodine_c_call_with(iodine_connection_on_pubsub_in_gvl, m);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
FIO_IFUNC VALUE iodine_connection_subscribe_internal(fio_io_s *io,
|
|
2040
|
+
int argc,
|
|
2041
|
+
VALUE *argv) {
|
|
2042
|
+
fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0);
|
|
2043
|
+
int64_t filter = 0;
|
|
2044
|
+
uint64_t since = 0;
|
|
2045
|
+
VALUE proc = Qnil;
|
|
2046
|
+
|
|
2047
|
+
iodine_rb2c_arg(argc,
|
|
2048
|
+
argv,
|
|
2049
|
+
IODINE_ARG_BUF(channel, 0, "channel", 0),
|
|
2050
|
+
IODINE_ARG_NUM(filter, 0, "filter", 0),
|
|
2051
|
+
IODINE_ARG_U64(since, 0, "since", 0),
|
|
2052
|
+
IODINE_ARG_PROC(proc, 0, "callback", 0));
|
|
2053
|
+
|
|
2054
|
+
if ((size_t)filter & (~(size_t)0xFFFF))
|
|
2055
|
+
rb_raise(rb_eRangeError,
|
|
2056
|
+
"filter out of range (%lld > 0xFFFF)",
|
|
2057
|
+
(long long)filter);
|
|
2058
|
+
if (!io && proc == Qnil)
|
|
2059
|
+
rb_raise(rb_eArgError,
|
|
2060
|
+
"Global subscriptions require a callback (proc/block) object!");
|
|
2061
|
+
STORE.hold(proc);
|
|
2062
|
+
fio_pubsub_subscribe(.io = io,
|
|
2063
|
+
.filter = (int16_t)filter,
|
|
2064
|
+
.channel = channel,
|
|
2065
|
+
.udata = (void *)proc,
|
|
2066
|
+
.queue = (!proc || (proc == Qnil)
|
|
2067
|
+
? NULL
|
|
2068
|
+
: fio_io_async_queue(&IODINE_THREAD_POOL)),
|
|
2069
|
+
.on_message = (!proc || (proc == Qnil)
|
|
2070
|
+
? NULL
|
|
2071
|
+
: iodine_connection_on_pubsub),
|
|
2072
|
+
.on_unsubscribe = (void (*)(void *))STORE.release,
|
|
2073
|
+
.replay_since = since);
|
|
2074
|
+
return proc;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
static VALUE iodine_connection_unsubscribe_internal(fio_io_s *io,
|
|
2078
|
+
int argc,
|
|
2079
|
+
VALUE *argv) {
|
|
2080
|
+
int64_t filter = 0;
|
|
2081
|
+
fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0);
|
|
2082
|
+
iodine_rb2c_arg(argc,
|
|
2083
|
+
argv,
|
|
2084
|
+
IODINE_ARG_BUF(channel, 0, "channel", 0),
|
|
2085
|
+
IODINE_ARG_NUM(filter, 0, "filter", 0));
|
|
2086
|
+
if ((size_t)filter & (~(size_t)0xFFFF))
|
|
2087
|
+
rb_raise(rb_eRangeError,
|
|
2088
|
+
"filter out of range (%lld > 0xFFFF)",
|
|
2089
|
+
(long long)filter);
|
|
2090
|
+
return fio_pubsub_unsubscribe(.io = io, .channel = channel, .filter = filter)
|
|
2091
|
+
? RUBY_Qfalse
|
|
2092
|
+
: RUBY_Qtrue;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/* *****************************************************************************
|
|
2096
|
+
Listening Argument Parsing
|
|
2097
|
+
***************************************************************************** */
|
|
2098
|
+
|
|
2099
|
+
/** C STL HTTP timeouts are `uint8_t` seconds; raise instead of truncating. */
|
|
2100
|
+
FIO_SFUNC uint8_t iodine_connection___cli_timeout(const char *opt) {
|
|
2101
|
+
int64_t t = fio_cli_get_i(opt);
|
|
2102
|
+
if (t < 0 || t > UINT8_MAX)
|
|
2103
|
+
rb_raise(rb_eRangeError, "CLI %s out of range (0..255 seconds)", opt);
|
|
2104
|
+
return (uint8_t)t;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
FIO_IFUNC iodine_connection_args_s iodine_connection_parse_args(int argc,
|
|
2108
|
+
VALUE *argv) {
|
|
2109
|
+
iodine_connection_args_s r = {
|
|
2110
|
+
.url = fio_cli_get_str("-b"),
|
|
2111
|
+
.rb_tls = ((fio_cli_get_bool("-tls") ||
|
|
2112
|
+
(fio_cli_get("-cert") && fio_cli_get("-key")))
|
|
2113
|
+
? Qtrue
|
|
2114
|
+
: Qnil),
|
|
2115
|
+
.headers = Qnil,
|
|
2116
|
+
.cookies = Qnil,
|
|
2117
|
+
.settings =
|
|
2118
|
+
{
|
|
2119
|
+
/* defaults */
|
|
2120
|
+
.on_http = iodine_io_http_on_http,
|
|
2121
|
+
.on_stop = iodine_io_http_on_stop,
|
|
2122
|
+
.on_finish = iodine_io_http_on_finish,
|
|
2123
|
+
.on_authenticate_sse = iodine_io_http_on_authenticate_sse,
|
|
2124
|
+
.on_authenticate_websocket =
|
|
2125
|
+
iodine_io_http_on_authenticate_websocket,
|
|
2126
|
+
.on_open = iodine_io_http_on_open,
|
|
2127
|
+
.on_message = iodine_io_http_on_message,
|
|
2128
|
+
.on_eventsource = iodine_io_http_on_eventsource,
|
|
2129
|
+
.on_eventsource_reconnect =
|
|
2130
|
+
iodine_io_http_on_eventsource_reconnect,
|
|
2131
|
+
.on_ready = iodine_io_http_on_ready,
|
|
2132
|
+
.on_shutdown = iodine_io_http_on_shutdown,
|
|
2133
|
+
.on_close = iodine_io_http_on_close,
|
|
2134
|
+
.queue = &IODINE_THREAD_POOL,
|
|
2135
|
+
.public_folder = FIO_STR_INFO1((char *)fio_cli_get("-www")),
|
|
2136
|
+
.max_age = (size_t)fio_cli_get_i("-maxage"),
|
|
2137
|
+
.max_header_size = (uint32_t)fio_cli_get_i("-maxhd"),
|
|
2138
|
+
.max_line_len = (uint32_t)fio_cli_get_i("-maxln"),
|
|
2139
|
+
.max_body_size = (size_t)fio_cli_get_i("-maxbd"),
|
|
2140
|
+
.ws_max_msg_size = (size_t)fio_cli_get_i("-maxms"),
|
|
2141
|
+
.timeout = iodine_connection___cli_timeout("-k"),
|
|
2142
|
+
.ws_timeout = iodine_connection___cli_timeout("-ping"),
|
|
2143
|
+
.sse_timeout = iodine_connection___cli_timeout("-ping"),
|
|
2144
|
+
/* deflate is on by default; CLI flags can disable it */
|
|
2145
|
+
.compress_static = !fio_cli_get_bool("-no-dynd"),
|
|
2146
|
+
.compress_ws = !fio_cli_get_bool("-no-wsd"),
|
|
2147
|
+
.log = fio_cli_get_bool("-v"),
|
|
2148
|
+
},
|
|
2149
|
+
};
|
|
2150
|
+
VALUE proc = Qnil, handler_tmp = Qnil, tls_io_rb = Qnil, timeout = Qnil;
|
|
2151
|
+
iodine_rb2c_arg(
|
|
2152
|
+
argc,
|
|
2153
|
+
argv,
|
|
2154
|
+
IODINE_ARG_BUF(r.url, 0, "url", 0),
|
|
2155
|
+
IODINE_ARG_RB(handler_tmp, 0, "handler", 0),
|
|
2156
|
+
IODINE_ARG_BUF(r.hint, 0, "service", 0),
|
|
2157
|
+
IODINE_ARG_RB(r.rb_tls, 0, "tls", 0),
|
|
2158
|
+
IODINE_ARG_RB(tls_io_rb, 0, "tls_io", 0),
|
|
2159
|
+
IODINE_ARG_STR(r.settings.public_folder, 0, "public", 0),
|
|
2160
|
+
IODINE_ARG_SIZE_T(r.settings.max_age, 0, "max_age", 0),
|
|
2161
|
+
IODINE_ARG_U32(r.settings.max_header_size, 0, "max_header_size", 0),
|
|
2162
|
+
IODINE_ARG_U32(r.settings.max_line_len, 0, "max_line_len", 0),
|
|
2163
|
+
IODINE_ARG_SIZE_T(r.settings.max_body_size, 0, "max_body_size", 0),
|
|
2164
|
+
IODINE_ARG_SIZE_T(r.settings.ws_max_msg_size, 0, "max_msg_size", 0),
|
|
2165
|
+
IODINE_ARG_U8(r.settings.timeout, 0, "timeout", 0),
|
|
2166
|
+
IODINE_ARG_U8(r.settings.ws_timeout, 0, "ping", 0),
|
|
2167
|
+
IODINE_ARG_BOOL(r.settings.log, 0, "log", 0),
|
|
2168
|
+
IODINE_ARG_BOOL(r.settings.compress_static, 0, "dynamic_deflate", 0),
|
|
2169
|
+
IODINE_ARG_BOOL(r.settings.compress_ws, 0, "websocket_deflate", 0),
|
|
2170
|
+
IODINE_ARG_BUF(r.method, 0, "method", 0),
|
|
2171
|
+
IODINE_ARG_RB(r.headers, 0, "headers", 0),
|
|
2172
|
+
IODINE_ARG_BUF(r.body, 0, "body", 0),
|
|
2173
|
+
IODINE_ARG_RB(r.cookies, 0, "cookies", 0),
|
|
2174
|
+
IODINE_ARG_PROC(proc, 0, "block", 0));
|
|
2175
|
+
r.settings.compress_dynamic = r.settings.compress_static;
|
|
2176
|
+
r.settings.udata = (void *)handler_tmp;
|
|
2177
|
+
/* test for errors before allocating or protecting data */
|
|
2178
|
+
|
|
2179
|
+
if (r.headers != Qnil && !RB_TYPE_P(r.headers, RUBY_T_HASH))
|
|
2180
|
+
rb_raise(rb_eTypeError, "headers (if provided) MUST be type Hash");
|
|
2181
|
+
if (r.cookies != Qnil && !RB_TYPE_P(r.cookies, RUBY_T_HASH))
|
|
2182
|
+
rb_raise(rb_eTypeError, "cookies (if provided) MUST be type Hash");
|
|
2183
|
+
|
|
2184
|
+
if (!r.settings.udata || (VALUE)r.settings.udata == Qnil) {
|
|
2185
|
+
r.settings.udata = (void *)proc;
|
|
2186
|
+
if (proc == Qnil && r.settings.public_folder.buf)
|
|
2187
|
+
r.settings.udata = (void *)iodine_rb_IODINE_BASE_APP404;
|
|
2188
|
+
}
|
|
2189
|
+
if (IODINE_STORE_IS_SKIP(((VALUE)r.settings.udata)))
|
|
2190
|
+
rb_raise(
|
|
2191
|
+
rb_eArgError,
|
|
2192
|
+
"Either a `:handler` or `&block` must be provided and a valid Object!");
|
|
2193
|
+
/* the TIMEOUT constant only applies to class / module handlers (looking
|
|
2194
|
+
* up a constant on other object types, e.g., a Proc, is unsafe) */
|
|
2195
|
+
if (RB_TYPE_P((VALUE)r.settings.udata, RUBY_T_MODULE) ||
|
|
2196
|
+
RB_TYPE_P((VALUE)r.settings.udata, RUBY_T_CLASS)) {
|
|
2197
|
+
if (rb_const_defined((VALUE)r.settings.udata, IODINE_TIMEOUT_ID))
|
|
2198
|
+
timeout = rb_const_get((VALUE)r.settings.udata, IODINE_TIMEOUT_ID);
|
|
2199
|
+
}
|
|
2200
|
+
if (RB_INTEGER_TYPE_P(timeout)) /* C STL stores `uint8_t` seconds */
|
|
2201
|
+
r.settings.ws_timeout =
|
|
2202
|
+
(uint8_t)iodine___rb2c_arg2u(timeout, UINT8_MAX, "TIMEOUT");
|
|
2203
|
+
if (r.url.buf)
|
|
2204
|
+
r.url_data = fio_url_parse(r.url.buf, r.url.len);
|
|
2205
|
+
if (!r.hint.len)
|
|
2206
|
+
r.hint = r.url_data.scheme;
|
|
2207
|
+
if (r.hint.len > 8)
|
|
2208
|
+
rb_raise(rb_eArgError, "service hint too long");
|
|
2209
|
+
FIO_LOG_DDEBUG("iodine connection hint: %.*s", (int)r.hint.len, r.hint.buf);
|
|
2210
|
+
if (!r.hint.buf || !r.hint.len ||
|
|
2211
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"sses", 3)) ||
|
|
2212
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"sses", 4)) ||
|
|
2213
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"SSES", 3)) ||
|
|
2214
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"SSES", 4)) ||
|
|
2215
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"unixs", 4)) ||
|
|
2216
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"UNIXS", 4)) ||
|
|
2217
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"unixs", 5)) ||
|
|
2218
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"UNIXS", 5)) ||
|
|
2219
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"files", 4)) ||
|
|
2220
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"FILES", 4)) ||
|
|
2221
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"files", 5)) ||
|
|
2222
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"FILES", 5)) ||
|
|
2223
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"https", 4)) ||
|
|
2224
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"HTTPS", 4)) ||
|
|
2225
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"https", 5)) ||
|
|
2226
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"HTTPS", 5)))
|
|
2227
|
+
r.service = IODINE_SERVICE_HTTP;
|
|
2228
|
+
else if (FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"wss", 2)) ||
|
|
2229
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"WSS", 2)) ||
|
|
2230
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"wss", 3)) ||
|
|
2231
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"WSS", 3)))
|
|
2232
|
+
r.service = IODINE_SERVICE_WS;
|
|
2233
|
+
else if (FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"tcps", 3)) ||
|
|
2234
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"TCPS", 3)) ||
|
|
2235
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"tcps", 4)) ||
|
|
2236
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"TCPS", 4)) ||
|
|
2237
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"raws", 3)) ||
|
|
2238
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"RAWS", 3)) ||
|
|
2239
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"raws", 4)) ||
|
|
2240
|
+
FIO_BUF_INFO_IS_EQ(r.hint, FIO_BUF_INFO2((char *)"RAWS", 4)))
|
|
2241
|
+
r.service = IODINE_SERVICE_RAW;
|
|
2242
|
+
else {
|
|
2243
|
+
rb_raise(rb_eArgError,
|
|
2244
|
+
"URL scheme or service type hint error!\n\t"
|
|
2245
|
+
"Must be either https, http, ws, wss, sse, sses, tcp or tcps "
|
|
2246
|
+
"(case sensitive).");
|
|
2247
|
+
r.service = IODINE_SERVICE_UNKNOWN;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
/* No errors, start protecting Ruby data from GC */
|
|
2251
|
+
STORE.hold((VALUE)r.settings.udata);
|
|
2252
|
+
/* make sure all proper callbacks are defined. */
|
|
2253
|
+
iodine_handler_method_injection(Qnil, (VALUE)r.settings.udata);
|
|
2254
|
+
|
|
2255
|
+
r.settings.sse_timeout = r.settings.ws_timeout;
|
|
2256
|
+
|
|
2257
|
+
if (fio_url_is_tls(r.url_data).tls && (!r.rb_tls || r.rb_tls == Qnil))
|
|
2258
|
+
r.rb_tls = Qtrue;
|
|
2259
|
+
if (r.rb_tls != Qnil && r.rb_tls != Qfalse) {
|
|
2260
|
+
rb_ivar_set((VALUE)r.settings.udata,
|
|
2261
|
+
rb_intern2("__iodine__internal__tls", 23),
|
|
2262
|
+
r.rb_tls);
|
|
2263
|
+
|
|
2264
|
+
r.settings.tls =
|
|
2265
|
+
(r.rb_tls == Qtrue ? fio_io_tls_new()
|
|
2266
|
+
: fio_io_tls_dup(iodine_tls_get(r.rb_tls)));
|
|
2267
|
+
if (r.rb_tls == Qtrue && fio_cli_get("-cert") && fio_cli_get("-key"))
|
|
2268
|
+
fio_io_tls_cert_add(r.settings.tls,
|
|
2269
|
+
fio_cli_get("-name"),
|
|
2270
|
+
fio_cli_get("-cert"),
|
|
2271
|
+
fio_cli_get("-key"),
|
|
2272
|
+
fio_cli_get("-tls-pass"));
|
|
2273
|
+
/* Set TLS IO functions based on tls_io option */
|
|
2274
|
+
if (tls_io_rb == Qnil)
|
|
2275
|
+
tls_io_rb = rb_funcall(iodine_rb_IODINE_TLS, rb_intern("default"), 0);
|
|
2276
|
+
|
|
2277
|
+
if (tls_io_rb != Qnil) {
|
|
2278
|
+
if (!RB_TYPE_P(tls_io_rb, RUBY_T_SYMBOL))
|
|
2279
|
+
rb_raise(rb_eTypeError,
|
|
2280
|
+
"tls_io must be a Symbol (:iodine or :openssl)");
|
|
2281
|
+
ID tls_io_id = rb_sym2id(tls_io_rb);
|
|
2282
|
+
if (tls_io_id == IODINE_TLS_IO_IODINE_ID) {
|
|
2283
|
+
/* Embedded TLS 1.3 is always available via fio-stl.h */
|
|
2284
|
+
static fio_io_functions_s tls13_funcs;
|
|
2285
|
+
if (!tls13_funcs.build_context)
|
|
2286
|
+
tls13_funcs = fio_tls13_io_functions();
|
|
2287
|
+
r.settings.tls_io_func = &tls13_funcs;
|
|
2288
|
+
} else if (tls_io_id == IODINE_TLS_IO_OPENSSL_ID) {
|
|
2289
|
+
#ifdef HAVE_OPENSSL
|
|
2290
|
+
static fio_io_functions_s openssl_funcs;
|
|
2291
|
+
if (!openssl_funcs.build_context)
|
|
2292
|
+
openssl_funcs = fio_openssl_io_functions();
|
|
2293
|
+
r.settings.tls_io_func = &openssl_funcs;
|
|
2294
|
+
#else
|
|
2295
|
+
rb_raise(rb_eRuntimeError,
|
|
2296
|
+
"OpenSSL TLS backend not available (not compiled in)");
|
|
55
2297
|
#endif
|
|
2298
|
+
} else {
|
|
2299
|
+
rb_raise(rb_eArgError, "tls_io must be :iodine or :openssl");
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return r;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
/* *****************************************************************************
|
|
2307
|
+
Ruby Public API.
|
|
2308
|
+
***************************************************************************** */
|
|
2309
|
+
|
|
2310
|
+
static void iodine_connection___client_headers_add(fio_http_s *h,
|
|
2311
|
+
fio_str_info_s n,
|
|
2312
|
+
fio_str_info_s v) {
|
|
2313
|
+
char *eol = (char *)memchr(v.buf, '\n', v.len);
|
|
2314
|
+
while (eol) {
|
|
2315
|
+
++eol;
|
|
2316
|
+
fio_str_info_s tmp = FIO_STR_INFO2(v.buf, (size_t)(eol - v.buf));
|
|
2317
|
+
fio_http_request_header_add(h, n, tmp);
|
|
2318
|
+
v.buf = eol;
|
|
2319
|
+
v.len -= tmp.len;
|
|
2320
|
+
}
|
|
2321
|
+
fio_http_request_header_add(h, n, v);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
static int iodine_connection___client_headers(VALUE n, VALUE v, VALUE h_) {
|
|
2325
|
+
FIO_STR_INFO_TMP_VAR(num, 32);
|
|
2326
|
+
fio_http_s *h = (fio_http_s *)h_;
|
|
2327
|
+
fio_str_info_s name;
|
|
2328
|
+
fio_str_info_s val;
|
|
2329
|
+
if (RB_TYPE_P(v, RUBY_T_ARRAY))
|
|
2330
|
+
goto is_array;
|
|
2331
|
+
if (RB_TYPE_P(n, RUBY_T_SYMBOL))
|
|
2332
|
+
n = rb_sym_to_s(n);
|
|
2333
|
+
if (!RB_TYPE_P(n, RUBY_T_STRING))
|
|
2334
|
+
return ST_CONTINUE;
|
|
2335
|
+
name = (fio_str_info_s)IODINE_RSTR_INFO(n);
|
|
2336
|
+
|
|
2337
|
+
if (RB_TYPE_P(v, RUBY_T_SYMBOL))
|
|
2338
|
+
v = rb_sym_to_s(v);
|
|
2339
|
+
else if (RB_TYPE_P(v, RUBY_T_BIGNUM)) /* Bignum (Windows: Integers >= 2^30) */
|
|
2340
|
+
v = rb_big2str(v, 10);
|
|
2341
|
+
if (RB_TYPE_P(v, RUBY_T_STRING))
|
|
2342
|
+
val = (fio_str_info_s)IODINE_RSTR_INFO(v);
|
|
2343
|
+
else if (RB_TYPE_P(v, RUBY_T_FIXNUM) &&
|
|
2344
|
+
!fio_string_write_i(&num, NULL, NUM2LONG(v)))
|
|
2345
|
+
val = num;
|
|
2346
|
+
else
|
|
2347
|
+
return ST_CONTINUE;
|
|
2348
|
+
|
|
2349
|
+
iodine_connection___client_headers_add(h, name, val);
|
|
2350
|
+
return ST_CONTINUE;
|
|
2351
|
+
is_array:
|
|
2352
|
+
for (size_t i = 0; i < (size_t)RARRAY_LEN(v); ++i) {
|
|
2353
|
+
VALUE t = RARRAY_PTR(v)[i];
|
|
2354
|
+
if (!RB_TYPE_P(t, RUBY_T_STRING))
|
|
2355
|
+
continue;
|
|
2356
|
+
iodine_connection___client_headers_add(h,
|
|
2357
|
+
(fio_str_info_s)IODINE_RSTR_INFO(n),
|
|
2358
|
+
(fio_str_info_s)IODINE_RSTR_INFO(t));
|
|
2359
|
+
}
|
|
2360
|
+
return ST_CONTINUE;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
static int iodine_connection___client_cookie(VALUE n, VALUE v, VALUE h_) {
|
|
2364
|
+
fio_http_s *h = (fio_http_s *)h_;
|
|
2365
|
+
if (!RB_TYPE_P(n, RUBY_T_STRING) || !RB_TYPE_P(v, RUBY_T_STRING))
|
|
2366
|
+
goto not_string;
|
|
2367
|
+
fio_http_cookie_set(h,
|
|
2368
|
+
.name = IODINE_RSTR_INFO(n),
|
|
2369
|
+
.value = IODINE_RSTR_INFO(v));
|
|
2370
|
+
return ST_CONTINUE;
|
|
2371
|
+
|
|
2372
|
+
not_string:
|
|
2373
|
+
FIO_LOG_WARNING("Client cookie name and value MUST be type String");
|
|
2374
|
+
return ST_CONTINUE;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
typedef struct {
|
|
2378
|
+
VALUE connection;
|
|
2379
|
+
VALUE handler;
|
|
2380
|
+
fio_io_protocol_s *protocol;
|
|
2381
|
+
} iodine_io_raw_client_cleanup_s;
|
|
2382
|
+
|
|
2383
|
+
/** Releases raw-client resources after Ruby-facing cleanup. */
|
|
2384
|
+
static void *iodine_io_raw_client_cleanup_without_gvl(void *args_) {
|
|
2385
|
+
iodine_io_raw_client_cleanup_s *args =
|
|
2386
|
+
(iodine_io_raw_client_cleanup_s *)args_;
|
|
2387
|
+
if (args->handler && args->handler != Qnil)
|
|
2388
|
+
STORE.release(args->handler);
|
|
2389
|
+
if (args->connection && args->connection != Qnil)
|
|
2390
|
+
STORE.release(args->connection);
|
|
2391
|
+
if (args->protocol)
|
|
2392
|
+
FIO_MEM_FREE(args->protocol, sizeof(*args->protocol));
|
|
2393
|
+
return NULL;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
/** Called after the connection was closed (called once per IO). */
|
|
2397
|
+
static void *iodine_io_raw_client_on_close_in_gvl(void *args_) {
|
|
2398
|
+
iodine_io_raw_client_cleanup_s *args =
|
|
2399
|
+
(iodine_io_raw_client_cleanup_s *)args_;
|
|
2400
|
+
iodine_connection_s *c = iodine_connection_ptr(args->connection);
|
|
2401
|
+
if (!c)
|
|
2402
|
+
return NULL;
|
|
2403
|
+
/* `c->io` may be NULL if the IO was never attached (e.g., an early failure
|
|
2404
|
+
* path already cleared it). The protocol is owned by the IO - if there's
|
|
2405
|
+
* no IO, `on_failed` owns the cleanup instead. */
|
|
2406
|
+
if (c->io)
|
|
2407
|
+
args->protocol = fio_io_protocol(c->io);
|
|
2408
|
+
args->handler = c->store[IODINE_CONNECTION_STORE_handler];
|
|
2409
|
+
c->io = NULL;
|
|
2410
|
+
iodine_ruby_call_inside(args->handler,
|
|
2411
|
+
IODINE_ON_CLOSE_ID,
|
|
2412
|
+
1,
|
|
2413
|
+
&args->connection);
|
|
2414
|
+
c->store[IODINE_CONNECTION_STORE_handler] = Qnil;
|
|
2415
|
+
return NULL;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
static void iodine_io_raw_client_on_close(void *buf, void *udata) {
|
|
2419
|
+
iodine_io_raw_client_cleanup_s args = {.connection = (VALUE)udata};
|
|
2420
|
+
if (!args.connection || args.connection == Qnil)
|
|
2421
|
+
return;
|
|
2422
|
+
iodine_c_call_with(iodine_io_raw_client_on_close_in_gvl, &args);
|
|
2423
|
+
iodine_c_call_without(iodine_io_raw_client_cleanup_without_gvl, &args);
|
|
2424
|
+
(void)buf;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
/** Clears a failed raw client's Ruby state while holding the GVL. */
|
|
2428
|
+
static void *iodine_io_raw_client_on_failed_in_gvl(void *args_) {
|
|
2429
|
+
iodine_io_raw_client_cleanup_s *args =
|
|
2430
|
+
(iodine_io_raw_client_cleanup_s *)args_;
|
|
2431
|
+
iodine_connection_s *c = iodine_connection_ptr(args->connection);
|
|
2432
|
+
if (!c)
|
|
2433
|
+
return NULL;
|
|
2434
|
+
args->handler = c->store[IODINE_CONNECTION_STORE_handler];
|
|
2435
|
+
c->io = NULL;
|
|
2436
|
+
iodine_ruby_call_inside(args->handler,
|
|
2437
|
+
IODINE_ON_CLOSE_ID,
|
|
2438
|
+
1,
|
|
2439
|
+
&args->connection);
|
|
2440
|
+
c->store[IODINE_CONNECTION_STORE_handler] = Qnil;
|
|
2441
|
+
return NULL;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
/** Releases an outgoing raw client's resources after a failed connection. */
|
|
2445
|
+
static void iodine_io_raw_client_on_failed(fio_io_protocol_s *protocol,
|
|
2446
|
+
void *udata) {
|
|
2447
|
+
iodine_io_raw_client_cleanup_s args = {
|
|
2448
|
+
.connection = (VALUE)udata,
|
|
2449
|
+
.protocol = protocol,
|
|
2450
|
+
};
|
|
2451
|
+
if (args.connection && args.connection != Qnil)
|
|
2452
|
+
iodine_c_call_with(iodine_io_raw_client_on_failed_in_gvl, &args);
|
|
2453
|
+
iodine_c_call_without(iodine_io_raw_client_cleanup_without_gvl, &args);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
/** Initializes a Connection object. */
|
|
2457
|
+
static VALUE iodine_connection_initialize(int argc, VALUE *argv, VALUE self) {
|
|
2458
|
+
// if (!argc)
|
|
2459
|
+
// rb_raise(rb_eException, "Iodine::Connection.new shouldn't be called!");
|
|
2460
|
+
STORE.hold(self);
|
|
2461
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2462
|
+
iodine_connection_args_s args = iodine_connection_parse_args(argc, argv);
|
|
2463
|
+
if (!args.url.len)
|
|
2464
|
+
rb_raise(
|
|
2465
|
+
rb_eException,
|
|
2466
|
+
"Iodine::Connection.new client requires a valid URL to connect to!\n\t"
|
|
2467
|
+
"See documentation, use tcp(s)://.../ or http(s)://.../ or "
|
|
2468
|
+
"ws(s):/.../ etc'");
|
|
2469
|
+
/* Keep the returned client and its handler alive until close/failure. */
|
|
2470
|
+
c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)args.settings.udata;
|
|
2471
|
+
c->flags |= IODINE_CONNECTION_CLIENT;
|
|
2472
|
+
|
|
2473
|
+
/* test if HTTP scheme */
|
|
2474
|
+
if (args.service == IODINE_SERVICE_HTTP ||
|
|
2475
|
+
args.service == IODINE_SERVICE_WS) {
|
|
2476
|
+
fio_http_s *h = fio_http_new();
|
|
2477
|
+
|
|
2478
|
+
if (args.headers != Qnil)
|
|
2479
|
+
rb_hash_foreach(args.headers,
|
|
2480
|
+
iodine_connection___client_headers,
|
|
2481
|
+
(VALUE)h);
|
|
2482
|
+
if (args.cookies != Qnil)
|
|
2483
|
+
rb_hash_foreach(args.cookies,
|
|
2484
|
+
iodine_connection___client_cookie,
|
|
2485
|
+
(VALUE)h);
|
|
2486
|
+
// args.settings.on_finish =
|
|
2487
|
+
c->io = fio_http_connect FIO_NOOP(args.url.buf, h, args.settings);
|
|
2488
|
+
c->http = h;
|
|
2489
|
+
fio_http_udata2_set(h, (void *)self);
|
|
2490
|
+
|
|
2491
|
+
} else { /* Raw Connection */
|
|
2492
|
+
fio_io_protocol_s *protocol =
|
|
2493
|
+
FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0);
|
|
2494
|
+
FIO_ASSERT_ALLOC(protocol);
|
|
2495
|
+
*protocol = IODINE_RAW_PROTOCOL;
|
|
2496
|
+
protocol->on_attach = iodine_io_raw_client_on_attach;
|
|
2497
|
+
protocol->on_close = iodine_io_raw_client_on_close;
|
|
2498
|
+
protocol->timeout = 1000UL * (uint32_t)args.settings.ws_timeout;
|
|
2499
|
+
c->io = fio_io_connect(args.url.buf,
|
|
2500
|
+
.protocol = protocol,
|
|
2501
|
+
.udata = (void *)self,
|
|
2502
|
+
.tls = args.settings.tls,
|
|
2503
|
+
.timeout = protocol->timeout,
|
|
2504
|
+
.on_failed = iodine_io_raw_client_on_failed);
|
|
2505
|
+
}
|
|
2506
|
+
return self;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
/** Returns true if the connection appears to be open (no known issues). */
|
|
2510
|
+
static VALUE iodine_connection_is_open(VALUE self) {
|
|
2511
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2512
|
+
if (c->http)
|
|
2513
|
+
return ((fio_http_is_websocket(c->http) && !fio_http_is_finished(c->http))
|
|
2514
|
+
? Qfalse
|
|
2515
|
+
: Qtrue);
|
|
2516
|
+
if (c->io)
|
|
2517
|
+
return (fio_io_is_open(c->io) ? Qtrue : Qfalse);
|
|
2518
|
+
return Qfalse;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/**
|
|
2522
|
+
* Returns the number of bytes that need to be sent before the next
|
|
2523
|
+
* `on_drained` callback is called.
|
|
2524
|
+
*/
|
|
2525
|
+
static VALUE iodine_connection_pending(VALUE self) {
|
|
2526
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2527
|
+
if (c && c->io)
|
|
2528
|
+
return RB_SIZE2NUM(((size_t)fio_io_backlog(c->io)));
|
|
2529
|
+
return RB_SIZE2NUM(((size_t)0));
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/** Schedules the connection to be closed. */
|
|
2533
|
+
static VALUE iodine_connection_close(VALUE self) {
|
|
2534
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2535
|
+
if (c) {
|
|
2536
|
+
if (c->http) {
|
|
2537
|
+
/* avoid `rack.input` call to `close` closing the HTTP connection. */
|
|
2538
|
+
if (fio_http_is_upgraded(c->http))
|
|
2539
|
+
fio_http_close(c->http);
|
|
2540
|
+
else /* this is an `env['rack.input'].close` call */
|
|
2541
|
+
fio_http_body_close(c->http);
|
|
2542
|
+
} else if (c->io)
|
|
2543
|
+
fio_io_close(c->io);
|
|
2544
|
+
c->flags |= IODINE_CONNECTION_CLOSED;
|
|
2545
|
+
}
|
|
2546
|
+
return self;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* @deprecated use `Server.extensions[:pubsub]` instead.
|
|
2551
|
+
*
|
|
2552
|
+
* Always returns true, since Iodine connections support the pub/sub
|
|
2553
|
+
* extension.
|
|
2554
|
+
*/
|
|
2555
|
+
static VALUE iodine_connection_has_pubsub(VALUE self) {
|
|
2556
|
+
FIO_LOG_WARNING(
|
|
2557
|
+
"pubsub? is deprecated. Test using `Server.extensions[:pubsub]` or "
|
|
2558
|
+
"`#respond_to?(:subscribe)` instead.");
|
|
2559
|
+
return Qtrue;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
/** Writes data to the connection asynchronously. */
|
|
2563
|
+
static VALUE iodine_connection_write_header___value(fio_http_s *h,
|
|
2564
|
+
fio_str_info_s n,
|
|
2565
|
+
VALUE v,
|
|
2566
|
+
size_t depth) {
|
|
2567
|
+
VALUE r = Qtrue;
|
|
2568
|
+
FIO_STR_INFO_TMP_VAR(vstr, 24);
|
|
2569
|
+
|
|
2570
|
+
if (depth > 31)
|
|
2571
|
+
goto too_deep;
|
|
2572
|
+
if (v == Qnil || v == Qfalse)
|
|
2573
|
+
goto skip;
|
|
2574
|
+
|
|
2575
|
+
if (RB_TYPE_P(v, RUBY_T_ARRAY))
|
|
2576
|
+
goto is_array;
|
|
2577
|
+
if (RB_TYPE_P(v, RUBY_T_SYMBOL))
|
|
2578
|
+
v = rb_sym_to_s(v);
|
|
2579
|
+
else if (RB_TYPE_P(v, RUBY_T_BIGNUM)) /* Bignum (Windows: Integers >= 2^30) */
|
|
2580
|
+
v = rb_big2str(v, 10);
|
|
2581
|
+
if (RB_TYPE_P(v, RUBY_T_STRING))
|
|
2582
|
+
vstr = (fio_str_info_s)IODINE_RSTR_INFO(v);
|
|
2583
|
+
else if (RB_TYPE_P(v, RUBY_T_FIXNUM))
|
|
2584
|
+
fio_string_write_i(&vstr, NULL, (int64_t)RB_NUM2LL(v));
|
|
2585
|
+
else
|
|
2586
|
+
goto type_error;
|
|
2587
|
+
iodine_connection___add_header(h, n, vstr);
|
|
2588
|
+
return r;
|
|
2589
|
+
|
|
2590
|
+
is_array:
|
|
2591
|
+
for (size_t i = 0; i < (size_t)RARRAY_LEN(v); ++i) {
|
|
2592
|
+
VALUE t = RARRAY_PTR(v)[i];
|
|
2593
|
+
iodine_connection_write_header___value(h, n, t, depth + 1);
|
|
2594
|
+
}
|
|
2595
|
+
return r;
|
|
2596
|
+
|
|
2597
|
+
type_error:
|
|
2598
|
+
r = Qfalse;
|
|
2599
|
+
rb_raise(rb_eTypeError,
|
|
2600
|
+
"write_header called with a non-String header value(s).");
|
|
2601
|
+
return r;
|
|
2602
|
+
|
|
2603
|
+
too_deep:
|
|
2604
|
+
r = Qfalse;
|
|
2605
|
+
rb_raise(rb_eTypeError,
|
|
2606
|
+
"write_header called with deeply nested Arrays. Nesting should be "
|
|
2607
|
+
"avoided.");
|
|
2608
|
+
return r;
|
|
2609
|
+
|
|
2610
|
+
skip:
|
|
2611
|
+
return r;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
/** Writes data to the connection asynchronously. */
|
|
2615
|
+
static VALUE iodine_connection_write_header(VALUE self, VALUE name, VALUE v) {
|
|
2616
|
+
VALUE r = Qfalse;
|
|
2617
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2618
|
+
fio_http_s *h = c->http;
|
|
2619
|
+
if (!h || !fio_http_is_clean(h))
|
|
2620
|
+
return r;
|
|
2621
|
+
|
|
2622
|
+
fio_str_info_s header_rb;
|
|
2623
|
+
|
|
2624
|
+
if (RB_TYPE_P(name, RUBY_T_SYMBOL))
|
|
2625
|
+
name = rb_sym_to_s(name);
|
|
2626
|
+
if (!RB_TYPE_P(name, RUBY_T_STRING))
|
|
2627
|
+
goto type_error;
|
|
2628
|
+
|
|
2629
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = name;
|
|
2630
|
+
|
|
2631
|
+
header_rb = (fio_str_info_s)IODINE_RSTR_INFO(name);
|
|
2632
|
+
IODINE___COPY_TO_LOWER_CASE(n, header_rb);
|
|
2633
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
2634
|
+
|
|
2635
|
+
if (v == Qnil || v == Qfalse)
|
|
2636
|
+
goto clear_header;
|
|
2637
|
+
|
|
2638
|
+
r = iodine_connection_write_header___value(h, n, v, 0);
|
|
2639
|
+
return r;
|
|
2640
|
+
|
|
2641
|
+
clear_header:
|
|
2642
|
+
fio_http_response_header_set(h, n, FIO_STR_INFO0);
|
|
2643
|
+
return r;
|
|
2644
|
+
|
|
2645
|
+
type_error:
|
|
2646
|
+
r = Qfalse;
|
|
2647
|
+
rb_raise(rb_eTypeError, "write_header called with non-String header name.");
|
|
2648
|
+
return r;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
/** Writes data to the connection asynchronously. */
|
|
2652
|
+
FIO_IFUNC VALUE iodine_connection_write_internal(VALUE self,
|
|
2653
|
+
VALUE data,
|
|
2654
|
+
_Bool finish) {
|
|
2655
|
+
VALUE r = Qtrue;
|
|
2656
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2657
|
+
if (!c || (!c->http && !c->io)) {
|
|
2658
|
+
rb_check_funcall(data, IODINE_CLOSE_ID, 0, NULL);
|
|
2659
|
+
return (r = Qfalse);
|
|
2660
|
+
}
|
|
2661
|
+
fio_str_info_s to_write;
|
|
2662
|
+
unsigned to_copy = 1;
|
|
2663
|
+
unsigned is_text = 0;
|
|
2664
|
+
int fileno;
|
|
2665
|
+
void (*dealloc)(void *) = NULL;
|
|
2666
|
+
VALUE tmp;
|
|
2667
|
+
|
|
2668
|
+
/* TODO! SSE connections? id / event / data combo? */
|
|
2669
|
+
|
|
2670
|
+
if (RB_TYPE_P(data, RUBY_T_SYMBOL))
|
|
2671
|
+
data = rb_sym_to_s(data);
|
|
2672
|
+
if (RB_TYPE_P(data, RUBY_T_STRING)) {
|
|
2673
|
+
to_write = FIO_STR_INFO2(RSTRING_PTR(data), (size_t)RSTRING_LEN(data));
|
|
2674
|
+
is_text = (rb_enc_get(data) == IodineUTF8Encoding);
|
|
2675
|
+
} else if (data == Qnil) {
|
|
2676
|
+
to_write = FIO_STR_INFO0;
|
|
2677
|
+
} else if (c->http && fio_http_is_clean(c->http) &&
|
|
2678
|
+
rb_respond_to(data, IODINE_TO_PATH_ID)) {
|
|
2679
|
+
goto is_named_file;
|
|
2680
|
+
} else if (rb_respond_to(data, IODINE_FILENO_ID)) {
|
|
2681
|
+
goto is_file;
|
|
2682
|
+
} else {
|
|
2683
|
+
dealloc = (void (*)(void *))fio_bstr_free;
|
|
2684
|
+
to_copy = 0;
|
|
2685
|
+
to_write = fio_bstr_info(iodine_json_stringify2bstr(NULL, data));
|
|
2686
|
+
is_text = 1; /* JSON is always UTF-8 */
|
|
2687
|
+
}
|
|
2688
|
+
if (c->http) {
|
|
2689
|
+
if (fio_http_is_websocket(c->http))
|
|
2690
|
+
fio_http_websocket_write(c->http, to_write.buf, to_write.len, is_text);
|
|
2691
|
+
else
|
|
2692
|
+
fio_http_write(c->http,
|
|
2693
|
+
.buf = to_write.buf,
|
|
2694
|
+
.len = to_write.len,
|
|
2695
|
+
.dealloc = dealloc,
|
|
2696
|
+
.copy = to_copy,
|
|
2697
|
+
.finish = finish);
|
|
2698
|
+
} else if (c->io) {
|
|
2699
|
+
fio_io_write2(c->io,
|
|
2700
|
+
.buf = to_write.buf,
|
|
2701
|
+
.len = to_write.len,
|
|
2702
|
+
.dealloc = dealloc,
|
|
2703
|
+
.copy = to_copy);
|
|
2704
|
+
if (finish)
|
|
2705
|
+
fio_io_close(c->io);
|
|
2706
|
+
}
|
|
2707
|
+
return r;
|
|
2708
|
+
is_named_file:
|
|
2709
|
+
tmp = rb_funcallv(data, IODINE_TO_PATH_ID, 0, NULL);
|
|
2710
|
+
if (fio_http_static_file_response(c->http,
|
|
2711
|
+
(fio_str_info_s)IODINE_RSTR_INFO(tmp),
|
|
2712
|
+
FIO_STR_INFO0,
|
|
2713
|
+
0))
|
|
2714
|
+
r = Qfalse;
|
|
2715
|
+
rb_check_funcall(data, IODINE_CLOSE_ID, 0, NULL);
|
|
2716
|
+
return r;
|
|
2717
|
+
is_file:
|
|
2718
|
+
fileno = fio_file_dup(FIX2INT(rb_funcallv(data, IODINE_FILENO_ID, 0, NULL)));
|
|
2719
|
+
if (rb_respond_to(data, IODINE_CLOSE_ID))
|
|
2720
|
+
rb_funcallv(data, IODINE_CLOSE_ID, 0, NULL);
|
|
2721
|
+
if (fileno == -1)
|
|
2722
|
+
return Qfalse;
|
|
2723
|
+
if (c->http)
|
|
2724
|
+
fio_http_write(c->http, .fd = fileno, .finish = finish);
|
|
2725
|
+
else if (c->io) {
|
|
2726
|
+
fio_io_write2(c->io, .fd = fileno);
|
|
2727
|
+
if (finish)
|
|
2728
|
+
fio_io_close(c->io);
|
|
2729
|
+
}
|
|
2730
|
+
return r;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
/** Writes data to the connection asynchronously. */
|
|
2734
|
+
static VALUE iodine_connection_write(VALUE self, VALUE data) {
|
|
2735
|
+
return iodine_connection_write_internal(self, data, 0);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
/** Writes data to the connection asynchronously. */
|
|
2739
|
+
static VALUE iodine_connection_write_sse(int argc, VALUE *argv, VALUE self) {
|
|
2740
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2741
|
+
if (!c || !c->http)
|
|
2742
|
+
return Qfalse;
|
|
2743
|
+
fio_buf_info_s to_write;
|
|
2744
|
+
fio_buf_info_s id = {0}, event = {0};
|
|
2745
|
+
VALUE data = Qnil;
|
|
2746
|
+
void (*dealloc)(void *) = NULL;
|
|
2747
|
+
iodine_rb2c_arg(argc,
|
|
2748
|
+
argv,
|
|
2749
|
+
IODINE_ARG_BUF(id, 0, "id", 0),
|
|
2750
|
+
IODINE_ARG_BUF(event, 0, "event", 0),
|
|
2751
|
+
IODINE_ARG_RB(data, 0, "data", 0));
|
|
2752
|
+
if (data == Qnil)
|
|
2753
|
+
return Qfalse; /* no data payload */
|
|
2754
|
+
if (RB_TYPE_P(data, RUBY_T_SYMBOL))
|
|
2755
|
+
data = rb_sym_to_s(data);
|
|
2756
|
+
if (RB_TYPE_P(data, RUBY_T_STRING)) {
|
|
2757
|
+
to_write = (fio_buf_info_s)IODINE_RSTR_INFO(data);
|
|
2758
|
+
} else if (rb_respond_to(data, IODINE_CLOSE_ID)) {
|
|
2759
|
+
rb_funcallv(data, IODINE_CLOSE_ID, 0, NULL);
|
|
2760
|
+
return Qfalse;
|
|
2761
|
+
} else if (fio_http_is_sse(c->http)) {
|
|
2762
|
+
dealloc = (void (*)(void *))fio_bstr_free;
|
|
2763
|
+
to_write = fio_bstr_buf(iodine_json_stringify2bstr(NULL, data));
|
|
2764
|
+
}
|
|
2765
|
+
/* don't test `fio_http_is_sse` earlier, as we may need to close files */
|
|
2766
|
+
if (to_write.len && fio_http_is_sse(c->http))
|
|
2767
|
+
fio_http_sse_write(c->http, .id = id, .event = event, .data = to_write);
|
|
2768
|
+
if (dealloc)
|
|
2769
|
+
dealloc(to_write.buf);
|
|
2770
|
+
return Qtrue;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
/** Writes data to the connection asynchronously. */
|
|
2774
|
+
static VALUE iodine_connection_finish(int argc, VALUE *argv, VALUE self) {
|
|
2775
|
+
VALUE data = Qnil;
|
|
2776
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_RB(data, 0, "data", 0));
|
|
2777
|
+
if (data != Qnil)
|
|
2778
|
+
return iodine_connection_write_internal(self, data, 1);
|
|
2779
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2780
|
+
if ((!c->http && !c->io) || (c->flags & IODINE_CONNECTION_CLIENT))
|
|
2781
|
+
return Qfalse;
|
|
2782
|
+
if (c->http) {
|
|
2783
|
+
if (fio_http_is_finished(c->http))
|
|
2784
|
+
return Qfalse;
|
|
2785
|
+
if (fio_http_is_streaming(c->http) || fio_http_status(c->http) < 400)
|
|
2786
|
+
fio_http_write(c->http, .finish = 1);
|
|
2787
|
+
else /* sending an empty error should test for a custom error page. */
|
|
2788
|
+
fio_http_send_error_response(c->http, fio_http_status(c->http));
|
|
2789
|
+
} else if (c->io) {
|
|
2790
|
+
if (!fio_io_is_open(c->io))
|
|
2791
|
+
return Qfalse;
|
|
2792
|
+
fio_io_close(c->io);
|
|
2793
|
+
}
|
|
2794
|
+
return Qtrue;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
/** Returns the peer's network address or `nil`. */
|
|
2798
|
+
static VALUE iodine_connection_peer_addr(VALUE self) {
|
|
2799
|
+
fio_buf_info_s buf = FIO_BUF_INFO0;
|
|
2800
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2801
|
+
if (c->io)
|
|
2802
|
+
buf = fio_sock_peer_addr(fio_io_fd(c->io));
|
|
2803
|
+
if (!buf.len)
|
|
2804
|
+
return Qnil;
|
|
2805
|
+
return rb_str_new(buf.buf, buf.len);
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
/** Returns the published peer's address. If unknown, returns {#peer_addr}. */
|
|
2809
|
+
static VALUE iodine_connection_from(VALUE self) {
|
|
2810
|
+
FIO_STR_INFO_TMP_VAR(addr, 128);
|
|
2811
|
+
fio_buf_info_s buf = FIO_BUF_INFO0;
|
|
2812
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2813
|
+
if (c->http) {
|
|
2814
|
+
fio_http_from(&addr, c->http);
|
|
2815
|
+
buf = FIO_STR2BUF_INFO(addr);
|
|
2816
|
+
} else if (c->io)
|
|
2817
|
+
buf = fio_sock_peer_addr(fio_io_fd(c->io));
|
|
2818
|
+
if (!buf.len)
|
|
2819
|
+
return Qnil;
|
|
2820
|
+
return rb_str_new(buf.buf, buf.len);
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
/* *****************************************************************************
|
|
2824
|
+
Ruby Public API - Peer Certificate (TLS Client Authentication)
|
|
2825
|
+
***************************************************************************** */
|
|
2826
|
+
|
|
2827
|
+
/**
|
|
2828
|
+
* Returns the peer's leaf certificate (an {Iodine::TLS::Certificate}), or
|
|
2829
|
+
* `nil` if unavailable (not a TLS connection, handshake incomplete, or the
|
|
2830
|
+
* peer sent no certificate).
|
|
2831
|
+
*/
|
|
2832
|
+
static VALUE iodine_connection_certificate(VALUE self) {
|
|
2833
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2834
|
+
fio_x509_cert_s cert = {0}; /* zeroed = new iteration loop (leaf first) */
|
|
2835
|
+
if (!c->io)
|
|
2836
|
+
return Qnil;
|
|
2837
|
+
if (fio_io_peer_info_next(c->io, &cert))
|
|
2838
|
+
return Qnil;
|
|
2839
|
+
return iodine_tls_certificate_new(&cert);
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
/**
|
|
2843
|
+
* Yields the peer's certificate chain, leaf certificate first, allocating an
|
|
2844
|
+
* {Iodine::TLS::Certificate} per certificate.
|
|
2845
|
+
*
|
|
2846
|
+
* Returns an Enumerator if no block is given.
|
|
2847
|
+
*/
|
|
2848
|
+
static VALUE iodine_connection_each_certificate(VALUE self) {
|
|
2849
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2850
|
+
fio_x509_cert_s cert = {0}; /* zeroed = new iteration loop (leaf first) */
|
|
2851
|
+
RETURN_ENUMERATOR(self, 0, NULL);
|
|
2852
|
+
if (!c->io)
|
|
2853
|
+
return self;
|
|
2854
|
+
/* NOTE: iodine_tls_certificate_new copies all fields eagerly, since the
|
|
2855
|
+
* parsed views are invalidated by the next fio_io_peer_info_next call. */
|
|
2856
|
+
while (fio_io_peer_info_next(c->io, &cert) == 0)
|
|
2857
|
+
rb_yield(iodine_tls_certificate_new(&cert));
|
|
2858
|
+
return self;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
/* *****************************************************************************
|
|
2862
|
+
Ruby Public API - Hijacking (Rack)
|
|
2863
|
+
***************************************************************************** */
|
|
2864
|
+
|
|
2865
|
+
/** Method for calling `env['rack.hijack'].call`. */
|
|
2866
|
+
static VALUE iodine_connection_rack_hijack(VALUE self) {
|
|
2867
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2868
|
+
if (!c->http)
|
|
2869
|
+
return Qnil;
|
|
2870
|
+
int new_fd = fio_sock_dup(fio_io_fd(fio_http_io(c->http)));
|
|
2871
|
+
VALUE nio = rb_io_fdopen(new_fd, O_RDWR, NULL);
|
|
2872
|
+
if (nio && nio != Qnil) {
|
|
2873
|
+
fio_http_close(c->http);
|
|
2874
|
+
fio_http_free(c->http);
|
|
2875
|
+
c->http = NULL;
|
|
2876
|
+
c->io = NULL;
|
|
2877
|
+
if (c->store[IODINE_CONNECTION_STORE_env] &&
|
|
2878
|
+
RB_TYPE_P(c->store[IODINE_CONNECTION_STORE_env], RUBY_T_HASH)) {
|
|
2879
|
+
rb_hash_aset(c->store[IODINE_CONNECTION_STORE_env],
|
|
2880
|
+
STORE.frozen_str(FIO_STR_INFO1((char *)"rack.hijack_io")),
|
|
2881
|
+
nio);
|
|
2882
|
+
}
|
|
2883
|
+
} else {
|
|
2884
|
+
fio_sock_close(new_fd);
|
|
2885
|
+
}
|
|
2886
|
+
return nio;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
/* Partial Hijack */
|
|
2890
|
+
FIO_SFUNC int iodine_connection_rack_hijack_partial(iodine_connection_s *c,
|
|
2891
|
+
VALUE proc) {
|
|
2892
|
+
if (!c->http && !c->io) {
|
|
2893
|
+
FIO_LOG_ERROR(
|
|
2894
|
+
"iodine_connection_rack_hijack_partial called without connections");
|
|
2895
|
+
return -1;
|
|
2896
|
+
}
|
|
2897
|
+
if (!c->io)
|
|
2898
|
+
c->io = fio_http_io(c->http);
|
|
2899
|
+
if (!rb_respond_to(proc, IODINE_CALL_ID))
|
|
2900
|
+
goto error;
|
|
2901
|
+
int new_fd = fio_sock_dup(fio_io_fd(c->io));
|
|
2902
|
+
if (new_fd == -1)
|
|
2903
|
+
goto error;
|
|
2904
|
+
VALUE nio = rb_io_fdopen(new_fd, O_RDWR, NULL);
|
|
2905
|
+
if (!nio || nio == Qnil)
|
|
2906
|
+
goto error_after_open;
|
|
2907
|
+
fio_http_write(c->http, .finish = 1);
|
|
2908
|
+
if (c->store[IODINE_CONNECTION_STORE_env] &&
|
|
2909
|
+
RB_TYPE_P(c->store[IODINE_CONNECTION_STORE_env], RUBY_T_HASH)) {
|
|
2910
|
+
rb_hash_aset(c->store[IODINE_CONNECTION_STORE_env],
|
|
2911
|
+
STORE.frozen_str(FIO_STR_INFO1((char *)"rack.hijack_io")),
|
|
2912
|
+
nio);
|
|
2913
|
+
}
|
|
2914
|
+
FIO_LOG_INFO("calling IO partial hijack `call`");
|
|
2915
|
+
rb_funcallv(proc, IODINE_CALL_ID, 1, &nio);
|
|
2916
|
+
return 0;
|
|
2917
|
+
error_after_open:
|
|
2918
|
+
fio_sock_close(new_fd);
|
|
2919
|
+
error:
|
|
2920
|
+
FIO_LOG_ERROR("partial hijack error – does the object answer to `call`?");
|
|
2921
|
+
return -1;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
/* *****************************************************************************
|
|
2925
|
+
Ruby Public API - Pub/Sub
|
|
2926
|
+
***************************************************************************** */
|
|
2927
|
+
|
|
2928
|
+
/**
|
|
2929
|
+
* Un-Subscribes to a combination of a channel (String) and filter (number).
|
|
2930
|
+
*
|
|
2931
|
+
* Accepts the following (possibly named) arguments and an optional block:
|
|
2932
|
+
*
|
|
2933
|
+
* - `channel`: the subscription's channel name (String).
|
|
2934
|
+
* - `filter`: the subscription's filter (Number < 32,767).
|
|
2935
|
+
*
|
|
2936
|
+
* i.e.:
|
|
2937
|
+
*
|
|
2938
|
+
* unsubscribe("name")
|
|
2939
|
+
* unsubscribe(channel: "name")
|
|
2940
|
+
* # or with filter
|
|
2941
|
+
* unsubscribe("name", 1)
|
|
2942
|
+
* unsubscribe(channel: "name", filter: 1)
|
|
2943
|
+
* # or only a filter
|
|
2944
|
+
* unsubscribe(nil, 1)
|
|
2945
|
+
* unsubscribe(filter: 1)
|
|
2946
|
+
*
|
|
2947
|
+
*/
|
|
2948
|
+
static VALUE iodine_connection_unsubscribe(int argc, VALUE *argv, VALUE self) {
|
|
2949
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
2950
|
+
if (!c || (!c->http && !c->io))
|
|
2951
|
+
return Qnil;
|
|
2952
|
+
return iodine_connection_unsubscribe_internal(c->io, argc, argv);
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
/**
|
|
2956
|
+
* Un-Subscribes to a combination of a channel (String) and filter (number).
|
|
2957
|
+
*
|
|
2958
|
+
* Accepts the following (possibly named) arguments and an optional block:
|
|
2959
|
+
*
|
|
2960
|
+
* - `channel`: the subscription's channel name (String).
|
|
2961
|
+
* - `filter`: the subscription's filter (Number < 32,767).
|
|
2962
|
+
*
|
|
2963
|
+
* i.e.:
|
|
2964
|
+
*
|
|
2965
|
+
* unsubscribe("name")
|
|
2966
|
+
* unsubscribe(channel: "name")
|
|
2967
|
+
* # or with filter
|
|
2968
|
+
* unsubscribe("name", 1)
|
|
2969
|
+
* unsubscribe(channel: "name", filter: 1)
|
|
2970
|
+
* # or only a filter
|
|
2971
|
+
* unsubscribe(nil, 1)
|
|
2972
|
+
* unsubscribe(filter: 1)
|
|
2973
|
+
*
|
|
2974
|
+
*/
|
|
2975
|
+
static VALUE iodine_connection_unsubscribe_klass(int argc,
|
|
2976
|
+
VALUE *argv,
|
|
2977
|
+
VALUE self) {
|
|
2978
|
+
return iodine_connection_unsubscribe_internal(NULL, argc, argv);
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
/**
|
|
2982
|
+
* Subscribes to a combination of a channel (String) and filter (number).
|
|
2983
|
+
*
|
|
2984
|
+
* Accepts the following (possibly named) arguments and an optional block:
|
|
2985
|
+
*
|
|
2986
|
+
* - `channel`: the subscription's channel name (String).
|
|
2987
|
+
* - `filter`: the subscription's filter (Number < 32,767).
|
|
2988
|
+
* - `since`: replay cached messages since this timestamp in milliseconds
|
|
2989
|
+
* (Number, requires history to be enabled).
|
|
2990
|
+
* - `callback`: an optional object that answers to `call` and accepts a
|
|
2991
|
+
* single argument (the message structure).
|
|
2992
|
+
*
|
|
2993
|
+
* The message structure (the argument passed to `proc`) answers to the
|
|
2994
|
+
* following:
|
|
2995
|
+
*
|
|
2996
|
+
* - `id`: the message's (somewhat unique) ID (Number).
|
|
2997
|
+
* - `channel`: the message's target channel name (String or `nil`).
|
|
2998
|
+
* - `filter`: the message's target filter (Number < 32,767).
|
|
2999
|
+
* - `message`: the message's content (String or `nil`).
|
|
3000
|
+
* - `published`: a Unix timestamp in Milliseconds (Number).
|
|
3001
|
+
*
|
|
3002
|
+
* i.e.:
|
|
3003
|
+
*
|
|
3004
|
+
* subscribe("name")
|
|
3005
|
+
* subscribe(channel: "name")
|
|
3006
|
+
* # or with filter
|
|
3007
|
+
* subscribe("name", 1)
|
|
3008
|
+
* subscribe(channel: "name", filter: 1)
|
|
3009
|
+
* # or only a filter
|
|
3010
|
+
* subscribe(nil, 1)
|
|
3011
|
+
* subscribe(filter: 1)
|
|
3012
|
+
* # with / without a callback
|
|
3013
|
+
* subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel == Qnil; }
|
|
3014
|
+
* subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; }
|
|
3015
|
+
* # with history replay (requires Iodine::PubSub::History)
|
|
3016
|
+
* subscribe(channel: "name", since: Time.now.to_i * 1000 - 60000)
|
|
3017
|
+
*
|
|
3018
|
+
*/
|
|
3019
|
+
static VALUE iodine_connection_subscribe(int argc, VALUE *argv, VALUE self) {
|
|
3020
|
+
iodine_connection_s *c = iodine_connection_ptr(self);
|
|
3021
|
+
if (!c || (!c->http && !c->io))
|
|
3022
|
+
return Qnil;
|
|
3023
|
+
if (c->io)
|
|
3024
|
+
iodine_connection_subscribe_internal(c->io, argc, argv);
|
|
3025
|
+
return self;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
/**
|
|
3029
|
+
* Subscribes to a combination of a channel (String) and filter (number).
|
|
3030
|
+
*
|
|
3031
|
+
* Accepts the following (possibly named) arguments and an optional block:
|
|
3032
|
+
*
|
|
3033
|
+
* - `channel`: the subscription's channel name (String).
|
|
3034
|
+
* - `filter`: the subscription's filter (Number < 32,767).
|
|
3035
|
+
* - `since`: replay cached messages since this timestamp in milliseconds
|
|
3036
|
+
* (Number, requires history to be enabled).
|
|
3037
|
+
* - `proc`: an optional object that answers to `call` and accepts a single
|
|
3038
|
+
* argument (the message structure).
|
|
3039
|
+
*
|
|
3040
|
+
* Either a `proc` or a `block` MUST be provided for global subscriptions.
|
|
3041
|
+
*
|
|
3042
|
+
* i.e.:
|
|
3043
|
+
*
|
|
3044
|
+
* Iodine.subscribe("name")
|
|
3045
|
+
* Iodine.subscribe(channel: "name")
|
|
3046
|
+
* # or with filter
|
|
3047
|
+
* Iodine.subscribe("name", 1)
|
|
3048
|
+
* Iodine.subscribe(channel: "name", filter: 1)
|
|
3049
|
+
* # or only a filter
|
|
3050
|
+
* Iodine.subscribe(nil, 1)
|
|
3051
|
+
* Iodine.subscribe(filter: 1)
|
|
3052
|
+
* # with / without a proc
|
|
3053
|
+
* Iodine.subscribe(filter: 1) {|msg| msg.filter == 1; msg.channel ==
|
|
3054
|
+
* Qnil;} Iodine.subscribe() {|msg| msg.filter == 1; msg.channel == Qnil; }
|
|
3055
|
+
* # with history replay (requires Iodine::PubSub::History.cache)
|
|
3056
|
+
* Iodine.subscribe(channel: "name", since: Time.now.to_i * 1000 - 60000)
|
|
3057
|
+
*
|
|
3058
|
+
*/
|
|
3059
|
+
static VALUE iodine_connection_subscribe_klass(int argc,
|
|
3060
|
+
VALUE *argv,
|
|
3061
|
+
VALUE self) {
|
|
3062
|
+
iodine_connection_subscribe_internal(NULL, argc, argv);
|
|
3063
|
+
return self;
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
static VALUE iodine_connection_publish_internal(fio_io_s *io,
|
|
3067
|
+
int argc,
|
|
3068
|
+
VALUE *argv,
|
|
3069
|
+
VALUE self) {
|
|
3070
|
+
VALUE message = Qnil;
|
|
3071
|
+
VALUE engine = Qnil;
|
|
3072
|
+
int64_t filter = 0;
|
|
3073
|
+
fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0);
|
|
3074
|
+
fio_buf_info_s msg = FIO_BUF_INFO2(NULL, 0);
|
|
3075
|
+
char *to_free = NULL;
|
|
3076
|
+
fio_pubsub_engine_s *eng = NULL;
|
|
3077
|
+
iodine_rb2c_arg(argc,
|
|
3078
|
+
argv,
|
|
3079
|
+
IODINE_ARG_BUF(channel, 0, "channel", 0),
|
|
3080
|
+
IODINE_ARG_RB(message, 0, "message", 0),
|
|
3081
|
+
IODINE_ARG_NUM(filter, 0, "filter", 0),
|
|
3082
|
+
IODINE_ARG_RB(engine, 0, "engine", 0));
|
|
3083
|
+
if ((size_t)filter & (~(size_t)0xFFFF))
|
|
3084
|
+
rb_raise(rb_eRangeError,
|
|
3085
|
+
"filter out of range (%lld > 0xFFFF)",
|
|
3086
|
+
(long long)filter);
|
|
3087
|
+
if (RB_TYPE_P(message, RUBY_T_SYMBOL))
|
|
3088
|
+
message = rb_sym_to_s(message);
|
|
3089
|
+
if (RB_TYPE_P(message, RUBY_T_STRING)) {
|
|
3090
|
+
msg = FIO_BUF_INFO2(RSTRING_PTR(message), (size_t)RSTRING_LEN(message));
|
|
3091
|
+
} else {
|
|
3092
|
+
to_free = iodine_json_stringify2bstr(NULL, message);
|
|
3093
|
+
msg = fio_bstr_buf(to_free);
|
|
3094
|
+
}
|
|
3095
|
+
if (engine != Qnil)
|
|
3096
|
+
eng = iodine_pubsub_eng_get(engine)->ptr;
|
|
3097
|
+
fio_pubsub_publish(.from = io,
|
|
3098
|
+
.filter = (int16_t)filter,
|
|
3099
|
+
.channel = channel,
|
|
3100
|
+
.message = msg,
|
|
3101
|
+
.engine = eng);
|
|
3102
|
+
fio_bstr_free(to_free);
|
|
3103
|
+
return self;
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Publishes a message to a combination of a channel (String) and filter
|
|
3107
|
+
* (number).
|
|
3108
|
+
*
|
|
3109
|
+
* Accepts the following (possibly named) arguments and an optional block:
|
|
3110
|
+
*
|
|
3111
|
+
* - `channel`: the channel name to publish to (String).
|
|
3112
|
+
* - `filter`: the filter to publish to (Number < 32,767).
|
|
3113
|
+
* - `message`: the actual message to publish.
|
|
3114
|
+
* - `engine`: the pub/sub engine to use (if not the default one).
|
|
3115
|
+
*
|
|
3116
|
+
* i.e.:
|
|
3117
|
+
*
|
|
3118
|
+
* Iodine.publish("channel_name", 0, "payload")
|
|
3119
|
+
* Iodine.publish(channel: "name", message: "payload")
|
|
3120
|
+
*/
|
|
3121
|
+
static VALUE iodine_connection_publish(int argc, VALUE *argv, VALUE self) {
|
|
3122
|
+
return iodine_connection_publish_internal(iodine_connection_ptr(self)->io,
|
|
3123
|
+
argc,
|
|
3124
|
+
argv,
|
|
3125
|
+
self);
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
/**
|
|
3129
|
+
* Publishes a message to a combination of a channel (String) and filter
|
|
3130
|
+
* (number).
|
|
3131
|
+
*
|
|
3132
|
+
* Accepts the following (possibly named) arguments and an optional block:
|
|
3133
|
+
*
|
|
3134
|
+
* - `channel`: the channel name to publish to (String).
|
|
3135
|
+
* - `filter`: the filter to publish to (Number < 32,767).
|
|
3136
|
+
* - `message`: the actual message to publish.
|
|
3137
|
+
* - `engine`: the pub/sub engine to use (if not the default one).
|
|
3138
|
+
*
|
|
3139
|
+
* i.e.:
|
|
3140
|
+
*
|
|
3141
|
+
* Iodine.publish("channel_name", 0, "payload")
|
|
3142
|
+
* Iodine.publish(channel: "name", message: "payload")
|
|
3143
|
+
*/
|
|
3144
|
+
static VALUE iodine_connection_publish_klass(int argc,
|
|
3145
|
+
VALUE *argv,
|
|
3146
|
+
VALUE self) {
|
|
3147
|
+
return iodine_connection_publish_internal(NULL, argc, argv, self);
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
/* *****************************************************************************
|
|
3151
|
+
Listen to incoming TCP/IP Connections
|
|
3152
|
+
***************************************************************************** */
|
|
3153
|
+
|
|
3154
|
+
static void iodine_tcp_on_stop(fio_io_protocol_s *p, void *udata) {
|
|
3155
|
+
/* TODO! call on_finish or something similar... maybe */
|
|
3156
|
+
// VALUE connection = rb_obj_alloc(iodine_rb_IODINE_CONNECTION);
|
|
3157
|
+
// STORE.hold(connection);
|
|
3158
|
+
// iodine_connection_s *c = iodine_connection_ptr(m);
|
|
3159
|
+
// c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)udata;
|
|
3160
|
+
// c->io = NULL;
|
|
3161
|
+
// c->http = NULL;
|
|
3162
|
+
// iodine_ruby_call_outside((VALUE)udata, IODINE_CLOSE_ID, 1, &connection);
|
|
3163
|
+
// STORE.release(connection);
|
|
3164
|
+
|
|
3165
|
+
STORE.release((VALUE)udata);
|
|
3166
|
+
FIO_MEM_FREE(p, sizeof(*p));
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
static void *iodine_tcp_listen(iodine_connection_args_s args) {
|
|
3170
|
+
fio_io_protocol_s *protocol = FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0);
|
|
3171
|
+
if (!protocol)
|
|
3172
|
+
return NULL;
|
|
3173
|
+
STORE.hold((VALUE)args.settings.udata);
|
|
3174
|
+
*protocol = IODINE_RAW_PROTOCOL;
|
|
3175
|
+
protocol->timeout = 1000UL * (uint32_t)args.settings.ws_timeout;
|
|
3176
|
+
FIO_LOG_DEBUG("iodine will listen for raw connections on %.*s",
|
|
3177
|
+
(int)args.url.len,
|
|
3178
|
+
args.url.buf);
|
|
3179
|
+
return fio_io_listen(.url = args.url.buf,
|
|
3180
|
+
.protocol = protocol,
|
|
3181
|
+
.udata = args.settings.udata,
|
|
3182
|
+
.tls = args.settings.tls,
|
|
3183
|
+
.on_stop = iodine_tcp_on_stop,
|
|
3184
|
+
.queue_for_accept = 0);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
/* *****************************************************************************
|
|
3188
|
+
Listen to incoming HTTP Connections
|
|
3189
|
+
***************************************************************************** */
|
|
3190
|
+
|
|
3191
|
+
static void iodine_io_http_on_stop(struct fio_http_settings_s *s) {
|
|
3192
|
+
STORE.release((VALUE)s->udata);
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
/* *****************************************************************************
|
|
3196
|
+
Listen function routing
|
|
3197
|
+
***************************************************************************** */
|
|
3198
|
+
|
|
3199
|
+
static VALUE iodine_listen_rb(int argc, VALUE *argv, VALUE self) {
|
|
3200
|
+
VALUE r = Qnil;
|
|
3201
|
+
iodine_connection_args_s s = iodine_connection_parse_args(argc, argv);
|
|
3202
|
+
void *listener = NULL;
|
|
3203
|
+
switch (s.service) {
|
|
3204
|
+
case IODINE_SERVICE_RAW: listener = iodine_tcp_listen(s); break;
|
|
3205
|
+
case IODINE_SERVICE_HTTP: /* fall through */
|
|
3206
|
+
case IODINE_SERVICE_UNKNOWN: /* fall through */
|
|
3207
|
+
case IODINE_SERVICE_WS:
|
|
3208
|
+
s.settings.on_stop = NULL; /* leak the memory, better than accidental.. */
|
|
3209
|
+
listener = fio_http_listen FIO_NOOP(s.url.buf, s.settings);
|
|
3210
|
+
}
|
|
3211
|
+
fio_io_tls_free(s.settings.tls);
|
|
3212
|
+
if (!listener) /* resources freed by fio_io_listen if it fails */
|
|
3213
|
+
rb_raise(rb_eRuntimeError, "Couldn't open listening socket.");
|
|
3214
|
+
r = iodine_listener_new(listener,
|
|
3215
|
+
(VALUE)s.settings.udata,
|
|
3216
|
+
s.service != IODINE_SERVICE_RAW);
|
|
3217
|
+
/* avoid listening to the same address/port combination again */
|
|
3218
|
+
fio_cli_set("-p", NULL);
|
|
3219
|
+
if (fio_cli_get("-b") &&
|
|
3220
|
+
fio_url_parse(fio_cli_get("-b"), strlen(fio_cli_get("-b"))).port.len)
|
|
3221
|
+
fio_cli_set("-b", NULL);
|
|
3222
|
+
return r;
|
|
3223
|
+
(void)self;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
/* *****************************************************************************
|
|
3227
|
+
Initialize Connection related STORE Cache
|
|
3228
|
+
***************************************************************************** */
|
|
3229
|
+
|
|
3230
|
+
static void iodine_connection_cache_common_strings(void) {
|
|
3231
|
+
fio_str_info_s common_headers[] = {
|
|
3232
|
+
FIO_STR_INFO1((char *)""),
|
|
3233
|
+
FIO_STR_INFO1((char *)"a-im"),
|
|
3234
|
+
FIO_STR_INFO1((char *)"accept"),
|
|
3235
|
+
FIO_STR_INFO1((char *)"accept-ch"),
|
|
3236
|
+
FIO_STR_INFO1((char *)"accept-charset"),
|
|
3237
|
+
FIO_STR_INFO1((char *)"accept-datetime"),
|
|
3238
|
+
FIO_STR_INFO1((char *)"accept-encoding"),
|
|
3239
|
+
FIO_STR_INFO1((char *)"accept-language"),
|
|
3240
|
+
FIO_STR_INFO1((char *)"accept-patch"),
|
|
3241
|
+
FIO_STR_INFO1((char *)"accept-ranges"),
|
|
3242
|
+
FIO_STR_INFO1((char *)"access-control-allow-credentials"),
|
|
3243
|
+
FIO_STR_INFO1((char *)"access-control-allow-headers"),
|
|
3244
|
+
FIO_STR_INFO1((char *)"access-control-allow-methods"),
|
|
3245
|
+
FIO_STR_INFO1((char *)"access-control-allow-origin"),
|
|
3246
|
+
FIO_STR_INFO1((char *)"access-control-expose-headers"),
|
|
3247
|
+
FIO_STR_INFO1((char *)"access-control-max-age"),
|
|
3248
|
+
FIO_STR_INFO1((char *)"access-control-request-headers"),
|
|
3249
|
+
FIO_STR_INFO1((char *)"access-control-request-method"),
|
|
3250
|
+
FIO_STR_INFO1((char *)"age"),
|
|
3251
|
+
FIO_STR_INFO1((char *)"allow"),
|
|
3252
|
+
FIO_STR_INFO1((char *)"alt-svc"),
|
|
3253
|
+
FIO_STR_INFO1((char *)"authorization"),
|
|
3254
|
+
FIO_STR_INFO1((char *)"cache-control"),
|
|
3255
|
+
FIO_STR_INFO1((char *)"common"),
|
|
3256
|
+
FIO_STR_INFO1((char *)"connection"),
|
|
3257
|
+
FIO_STR_INFO1((char *)"content-disposition"),
|
|
3258
|
+
FIO_STR_INFO1((char *)"content-encoding"),
|
|
3259
|
+
FIO_STR_INFO1((char *)"content-language"),
|
|
3260
|
+
FIO_STR_INFO1((char *)"content-length"),
|
|
3261
|
+
FIO_STR_INFO1((char *)"content-location"),
|
|
3262
|
+
FIO_STR_INFO1((char *)"content-md5"),
|
|
3263
|
+
FIO_STR_INFO1((char *)"content-range"),
|
|
3264
|
+
FIO_STR_INFO1((char *)"content-security-policy"),
|
|
3265
|
+
FIO_STR_INFO1((char *)"content-type"),
|
|
3266
|
+
FIO_STR_INFO1((char *)"cookie"),
|
|
3267
|
+
FIO_STR_INFO1((char *)"correlates"),
|
|
3268
|
+
FIO_STR_INFO1((char *)"date"),
|
|
3269
|
+
FIO_STR_INFO1((char *)"delta-base"),
|
|
3270
|
+
FIO_STR_INFO1((char *)"dnt"),
|
|
3271
|
+
FIO_STR_INFO1((char *)"etag"),
|
|
3272
|
+
FIO_STR_INFO1((char *)"example"),
|
|
3273
|
+
FIO_STR_INFO1((char *)"expect"),
|
|
3274
|
+
FIO_STR_INFO1((char *)"expect-ct"),
|
|
3275
|
+
FIO_STR_INFO1((char *)"expires"),
|
|
3276
|
+
FIO_STR_INFO1((char *)"field"),
|
|
3277
|
+
FIO_STR_INFO1((char *)"forwarded"),
|
|
3278
|
+
FIO_STR_INFO1((char *)"from"),
|
|
3279
|
+
FIO_STR_INFO1((char *)"front-end-https"),
|
|
3280
|
+
FIO_STR_INFO1((char *)"host"),
|
|
3281
|
+
FIO_STR_INFO1((char *)"http2-settings"),
|
|
3282
|
+
FIO_STR_INFO1((char *)"if-match"),
|
|
3283
|
+
FIO_STR_INFO1((char *)"if-modified-since"),
|
|
3284
|
+
FIO_STR_INFO1((char *)"if-none-match"),
|
|
3285
|
+
FIO_STR_INFO1((char *)"if-range"),
|
|
3286
|
+
FIO_STR_INFO1((char *)"if-unmodified-since"),
|
|
3287
|
+
FIO_STR_INFO1((char *)"im"),
|
|
3288
|
+
FIO_STR_INFO1((char *)"last-modified"),
|
|
3289
|
+
FIO_STR_INFO1((char *)"link"),
|
|
3290
|
+
FIO_STR_INFO1((char *)"location"),
|
|
3291
|
+
FIO_STR_INFO1((char *)"mandatory"),
|
|
3292
|
+
FIO_STR_INFO1((char *)"max-forwards"),
|
|
3293
|
+
FIO_STR_INFO1((char *)"must"),
|
|
3294
|
+
FIO_STR_INFO1((char *)"nel"),
|
|
3295
|
+
FIO_STR_INFO1((char *)"only"),
|
|
3296
|
+
FIO_STR_INFO1((char *)"origin"),
|
|
3297
|
+
FIO_STR_INFO1((char *)"p3p"),
|
|
3298
|
+
FIO_STR_INFO1((char *)"permanent"),
|
|
3299
|
+
FIO_STR_INFO1((char *)"permissions-policy"),
|
|
3300
|
+
FIO_STR_INFO1((char *)"pragma"),
|
|
3301
|
+
FIO_STR_INFO1((char *)"prefer"),
|
|
3302
|
+
FIO_STR_INFO1((char *)"preference-applied"),
|
|
3303
|
+
FIO_STR_INFO1((char *)"proxy-authenticate"),
|
|
3304
|
+
FIO_STR_INFO1((char *)"proxy-authorization"),
|
|
3305
|
+
FIO_STR_INFO1((char *)"proxy-connection"),
|
|
3306
|
+
FIO_STR_INFO1((char *)"public-key-pins"),
|
|
3307
|
+
FIO_STR_INFO1((char *)"range"),
|
|
3308
|
+
FIO_STR_INFO1((char *)"referer"),
|
|
3309
|
+
FIO_STR_INFO1((char *)"refresh"),
|
|
3310
|
+
FIO_STR_INFO1((char *)"report-to"),
|
|
3311
|
+
FIO_STR_INFO1((char *)"response"),
|
|
3312
|
+
FIO_STR_INFO1((char *)"retry-after"),
|
|
3313
|
+
FIO_STR_INFO1((char *)"rfc"),
|
|
3314
|
+
FIO_STR_INFO1((char *)"save-data"),
|
|
3315
|
+
FIO_STR_INFO1((char *)"sec-ch-ua"),
|
|
3316
|
+
FIO_STR_INFO1((char *)"sec-ch-ua-mobile"),
|
|
3317
|
+
FIO_STR_INFO1((char *)"sec-ch-ua-platform"),
|
|
3318
|
+
FIO_STR_INFO1((char *)"sec-fetch-dest"),
|
|
3319
|
+
FIO_STR_INFO1((char *)"sec-fetch-mode"),
|
|
3320
|
+
FIO_STR_INFO1((char *)"sec-fetch-site"),
|
|
3321
|
+
FIO_STR_INFO1((char *)"sec-fetch-user"),
|
|
3322
|
+
FIO_STR_INFO1((char *)"sec-gpc"),
|
|
3323
|
+
FIO_STR_INFO1((char *)"server"),
|
|
3324
|
+
FIO_STR_INFO1((char *)"set-cookie"),
|
|
3325
|
+
FIO_STR_INFO1((char *)"standard"),
|
|
3326
|
+
FIO_STR_INFO1((char *)"status"),
|
|
3327
|
+
FIO_STR_INFO1((char *)"strict-transport-security"),
|
|
3328
|
+
FIO_STR_INFO1((char *)"te"),
|
|
3329
|
+
FIO_STR_INFO1((char *)"timing-allow-origin"),
|
|
3330
|
+
FIO_STR_INFO1((char *)"tk"),
|
|
3331
|
+
FIO_STR_INFO1((char *)"trailer"),
|
|
3332
|
+
FIO_STR_INFO1((char *)"transfer-encoding"),
|
|
3333
|
+
FIO_STR_INFO1((char *)"upgrade"),
|
|
3334
|
+
FIO_STR_INFO1((char *)"upgrade-insecure-requests"),
|
|
3335
|
+
FIO_STR_INFO1((char *)"user-agent"),
|
|
3336
|
+
FIO_STR_INFO1((char *)"vary"),
|
|
3337
|
+
FIO_STR_INFO1((char *)"via"),
|
|
3338
|
+
FIO_STR_INFO1((char *)"warning"),
|
|
3339
|
+
FIO_STR_INFO1((char *)"when"),
|
|
3340
|
+
FIO_STR_INFO1((char *)"www-authenticate"),
|
|
3341
|
+
FIO_STR_INFO1((char *)"x-att-deviceid"),
|
|
3342
|
+
FIO_STR_INFO1((char *)"x-content-duration"),
|
|
3343
|
+
FIO_STR_INFO1((char *)"x-content-security-policy"),
|
|
3344
|
+
FIO_STR_INFO1((char *)"x-content-type-options"),
|
|
3345
|
+
FIO_STR_INFO1((char *)"x-correlation-id"),
|
|
3346
|
+
FIO_STR_INFO1((char *)"x-csrf-token"),
|
|
3347
|
+
FIO_STR_INFO1((char *)"x-forwarded-for"),
|
|
3348
|
+
FIO_STR_INFO1((char *)"x-forwarded-host"),
|
|
3349
|
+
FIO_STR_INFO1((char *)"x-forwarded-proto"),
|
|
3350
|
+
FIO_STR_INFO1((char *)"x-frame-options"),
|
|
3351
|
+
FIO_STR_INFO1((char *)"x-http-method-override"),
|
|
3352
|
+
FIO_STR_INFO1((char *)"x-powered-by"),
|
|
3353
|
+
FIO_STR_INFO1((char *)"x-redirect-by"),
|
|
3354
|
+
FIO_STR_INFO1((char *)"x-request-id"),
|
|
3355
|
+
FIO_STR_INFO1((char *)"x-requested-with"),
|
|
3356
|
+
FIO_STR_INFO1((char *)"x-ua-compatible"),
|
|
3357
|
+
FIO_STR_INFO1((char *)"x-uidh"),
|
|
3358
|
+
FIO_STR_INFO1((char *)"x-wap-profile"),
|
|
3359
|
+
FIO_STR_INFO1((char *)"x-webkit-csp"),
|
|
3360
|
+
FIO_STR_INFO1((char *)"x-xss-protection"),
|
|
3361
|
+
{0},
|
|
3362
|
+
};
|
|
3363
|
+
for (size_t i = 0; common_headers[i].buf; ++i) {
|
|
3364
|
+
STORE.release(STORE.frozen_str(common_headers[i]));
|
|
3365
|
+
STORE.release(STORE.header_name(common_headers[i]));
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
/* *****************************************************************************
|
|
3370
|
+
Initialize Connection Class
|
|
3371
|
+
***************************************************************************** */
|
|
3372
|
+
|
|
3373
|
+
/* Initialize Iodine::Connection */ // clang-format off
|
|
3374
|
+
/**
|
|
3375
|
+
* Iodine::Connection is the connection instance class used for all
|
|
3376
|
+
* connection callbacks to control the state of the connection.
|
|
3377
|
+
*/
|
|
3378
|
+
static void Init_Iodine_Connection(void) {
|
|
3379
|
+
rb_define_singleton_method(iodine_rb_IODINE_BASE,
|
|
3380
|
+
"add_missing_handler_methods",
|
|
3381
|
+
iodine_handler_method_injection, 1);
|
|
3382
|
+
rb_define_singleton_method(iodine_rb_IODINE,
|
|
3383
|
+
"make_resource",
|
|
3384
|
+
iodine_resource_handler_method_injection, -1);
|
|
3385
|
+
VALUE m = iodine_rb_IODINE_CONNECTION = rb_define_class_under(iodine_rb_IODINE, "Connection", rb_cObject);
|
|
3386
|
+
rb_define_alloc_func(m, iodine_connection_alloc);
|
|
3387
|
+
STORE.hold(iodine_rb_IODINE_CONNECTION);
|
|
3388
|
+
IODINE_CONST_ID_STORE(IODINE_SAME_SITE_DEFAULT, "default");
|
|
3389
|
+
IODINE_CONST_ID_STORE(IODINE_SAME_SITE_NONE, "none");
|
|
3390
|
+
IODINE_CONST_ID_STORE(IODINE_SAME_SITE_LAX, "lax");
|
|
3391
|
+
IODINE_CONST_ID_STORE(IODINE_SAME_SITE_STRICT, "strict");
|
|
3392
|
+
|
|
3393
|
+
IODINE_CONST_ID_STORE(IODINE_TLS_IO_IODINE_ID, "iodine");
|
|
3394
|
+
IODINE_CONST_ID_STORE(IODINE_TLS_IO_OPENSSL_ID, "openssl");
|
|
3395
|
+
|
|
3396
|
+
IODINE_CONST_ID_STORE(IODINE_TIMEOUT_ID, "TIMEOUT");
|
|
3397
|
+
|
|
3398
|
+
/* cache VALUE for `env` key */
|
|
3399
|
+
#define IODINE_CONST_RACK_STORE(val, str) \
|
|
3400
|
+
val = STORE.frozen_str(FIO_STR_INFO1((char *)str)); \
|
|
3401
|
+
STORE.hold(val)
|
|
3402
|
+
|
|
3403
|
+
IODINE_RACK_CONTENT_LENGTH = STORE.header_name(FIO_STR_INFO1((char *)"CONTENT_LENGTH"));
|
|
3404
|
+
STORE.hold(IODINE_RACK_CONTENT_LENGTH);
|
|
3405
|
+
|
|
3406
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_INPUT, "rack.input");
|
|
3407
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_NEORACK_CLIENT, "neorack.client");
|
|
3408
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_REQUEST_METHOD, "REQUEST_METHOD");
|
|
3409
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_PATH_INFO, "PATH_INFO");
|
|
3410
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_QUERY_STRING, "QUERY_STRING");
|
|
3411
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_SCRIPT_NAME, "SCRIPT_NAME");
|
|
3412
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_SERVER_NAME, "SERVER_NAME");
|
|
3413
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_SERVER_PROTOCOL, "SERVER_PROTOCOL");
|
|
3414
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_HTTP_VERSION, "HTTP_VERSION");
|
|
3415
|
+
IODINE_CONST_RACK_STORE(IODINE_RACK_REMOTE_ADDR, "REMOTE_ADDR");
|
|
3416
|
+
#undef IODINE_CONST_RACK_STORE
|
|
3417
|
+
|
|
3418
|
+
rb_define_method(m, "initialize", iodine_connection_initialize, -1);
|
|
3419
|
+
|
|
3420
|
+
#define IODINE_DEFINE_GETSET_METHOD(name)\
|
|
3421
|
+
rb_define_method(m, #name, iodine_connection_##name##_get, 0);\
|
|
3422
|
+
rb_define_method(m, #name "=", iodine_connection_##name##_set, 1)\
|
|
3423
|
+
|
|
3424
|
+
IODINE_DEFINE_GETSET_METHOD(env);
|
|
3425
|
+
IODINE_DEFINE_GETSET_METHOD(handler);
|
|
3426
|
+
IODINE_DEFINE_GETSET_METHOD(status);
|
|
3427
|
+
IODINE_DEFINE_GETSET_METHOD(method);
|
|
3428
|
+
IODINE_DEFINE_GETSET_METHOD(path);
|
|
3429
|
+
IODINE_DEFINE_GETSET_METHOD(opath);
|
|
3430
|
+
IODINE_DEFINE_GETSET_METHOD(query);
|
|
3431
|
+
IODINE_DEFINE_GETSET_METHOD(version);
|
|
3432
|
+
#undef IODINE_DEFINE_GETSET_METHOD
|
|
3433
|
+
|
|
3434
|
+
|
|
3435
|
+
|
|
3436
|
+
rb_define_method(m, "headers_sent?", iodine_connection_headers_sent_p, 0);
|
|
3437
|
+
rb_define_method(m, "valid?", iodine_connection_is_clean, 0);
|
|
3438
|
+
rb_define_method(m, "dup", iodine_connection_dup_failer, 0);
|
|
3439
|
+
rb_define_method(m, "to_s", iodine_connection_to_s, 0);
|
|
3440
|
+
|
|
3441
|
+
rb_define_method(m, "[]", iodine_connection_map_get, 1);
|
|
3442
|
+
rb_define_method(m, "[]=", iodine_connection_map_set, 2);
|
|
3443
|
+
|
|
3444
|
+
rb_define_method(m, "each", iodine_connection_map_each, 0);
|
|
3445
|
+
rb_define_method(m, "store_count", iodine_connection_map_count, 0);
|
|
3446
|
+
rb_define_method(m, "store_capa", iodine_connection_map_capa, 0);
|
|
3447
|
+
rb_define_method(m, "store_reserve", iodine_connection_map_reserve, 1);
|
|
3448
|
+
|
|
3449
|
+
rb_define_method(m, "headers", iodine_connection_map_headers, 0);
|
|
3450
|
+
rb_define_method(m, "rack_hijack", iodine_connection_rack_hijack, 0);
|
|
3451
|
+
|
|
3452
|
+
rb_define_method(m, "cookie", iodine_connection_cookie_get, 1);
|
|
3453
|
+
rb_define_method(m, "set_cookie", iodine_connection_cookie_set, -1);
|
|
3454
|
+
rb_define_method(m, "each_cookie", iodine_connection_cookie_each, 0);
|
|
3455
|
+
|
|
3456
|
+
rb_define_method(m, "length", iodine_connection_body_length, 0);
|
|
3457
|
+
rb_define_method(m, "gets", iodine_connection_body_gets, -1);
|
|
3458
|
+
rb_define_method(m, "read", iodine_connection_body_read, -1);
|
|
3459
|
+
rb_define_method(m, "seek", iodine_connection_body_seek, -1);
|
|
3460
|
+
rb_define_method(m, "rewind", iodine_connection_body_seek, -1);
|
|
3461
|
+
rb_define_method(m, "close_body", iodine_connection_body_close, 0);
|
|
3462
|
+
|
|
3463
|
+
rb_define_method(m, "open?", iodine_connection_is_open, 0);
|
|
3464
|
+
rb_define_method(m, "pending", iodine_connection_pending, 0);
|
|
3465
|
+
rb_define_method(m, "close", iodine_connection_close, 0);
|
|
3466
|
+
rb_define_method(m, "write_header", iodine_connection_write_header, 2);
|
|
3467
|
+
rb_define_method(m, "write", iodine_connection_write, 1);
|
|
3468
|
+
rb_define_method(m, "write_sse", iodine_connection_write_sse, -1);
|
|
3469
|
+
rb_define_method(m, "finish", iodine_connection_finish, -1);
|
|
3470
|
+
|
|
3471
|
+
|
|
3472
|
+
rb_define_method(m, "websocket?", iodine_connection_is_websocket, 0);
|
|
3473
|
+
rb_define_method(m, "sse?", iodine_connection_is_sse, 0);
|
|
3474
|
+
rb_define_method(m, "http?", iodine_connection_is_http, 0);
|
|
3475
|
+
rb_define_method(m, "tls?", iodine_connection_is_tls, 0);
|
|
3476
|
+
|
|
3477
|
+
rb_define_method(m, "peer_addr", iodine_connection_peer_addr, 0);
|
|
3478
|
+
rb_define_method(m, "from", iodine_connection_from, 0);
|
|
3479
|
+
rb_define_method(m, "certificate", iodine_connection_certificate, 0);
|
|
3480
|
+
rb_define_method(m,
|
|
3481
|
+
"each_certificate",
|
|
3482
|
+
iodine_connection_each_certificate,
|
|
3483
|
+
0);
|
|
3484
|
+
|
|
3485
|
+
rb_define_method(m, "subscribe", iodine_connection_subscribe, -1);
|
|
3486
|
+
rb_define_singleton_method(m, "subscribe", iodine_connection_subscribe_klass, -1);
|
|
3487
|
+
rb_define_singleton_method(iodine_rb_IODINE, "subscribe", iodine_connection_subscribe_klass, -1);
|
|
3488
|
+
|
|
3489
|
+
rb_define_method(m, "unsubscribe", iodine_connection_unsubscribe, -1);
|
|
3490
|
+
rb_define_singleton_method(m, "unsubscribe", iodine_connection_unsubscribe_klass, -1);
|
|
3491
|
+
rb_define_singleton_method(iodine_rb_IODINE, "unsubscribe", iodine_connection_unsubscribe_klass, -1);
|
|
3492
|
+
|
|
3493
|
+
rb_define_method(m, "publish", iodine_connection_publish, -1);
|
|
3494
|
+
rb_define_singleton_method(m, "publish", iodine_connection_publish_klass, -1);
|
|
3495
|
+
rb_define_singleton_method(iodine_rb_IODINE, "publish", iodine_connection_publish_klass, -1);
|
|
3496
|
+
|
|
3497
|
+
rb_define_method(m, "pubsub?", iodine_connection_has_pubsub, 0);
|
|
3498
|
+
rb_define_singleton_method(m, "pubsub?", iodine_connection_has_pubsub, 0);
|
|
3499
|
+
rb_define_singleton_method(iodine_rb_IODINE, "pubsub?", iodine_connection_has_pubsub, 0);
|
|
3500
|
+
|
|
3501
|
+
iodine_connection_cache_common_strings();
|
|
3502
|
+
iodine_connection_init_env_template(FIO_BUF_INFO0);
|
|
3503
|
+
} // clang-format on
|
|
3504
|
+
|
|
3505
|
+
#endif /* H___IODINE_CONNECTION___H */
|