iodine 0.4.19 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +22 -0
- data/LIMITS.md +19 -9
- data/README.md +92 -77
- data/SPEC-PubSub-Draft.md +113 -0
- data/SPEC-Websocket-Draft.md +127 -143
- data/bin/http-hello +0 -1
- data/bin/raw-rbhttp +1 -1
- data/bin/raw_broadcast +8 -10
- data/bin/updated api +2 -2
- data/bin/ws-broadcast +2 -4
- data/bin/ws-echo +2 -2
- data/examples/config.ru +13 -13
- data/examples/echo.ru +5 -6
- data/examples/hello.ru +2 -3
- data/examples/info.md +316 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/redis.ru +9 -9
- data/examples/shootout.ru +45 -11
- data/ext/iodine/defer.c +194 -297
- data/ext/iodine/defer.h +61 -53
- data/ext/iodine/evio.c +0 -260
- data/ext/iodine/evio.h +50 -22
- data/ext/iodine/evio_callbacks.c +26 -0
- data/ext/iodine/evio_epoll.c +251 -0
- data/ext/iodine/evio_kqueue.c +193 -0
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +1420 -542
- data/ext/iodine/facil.h +151 -64
- data/ext/iodine/fio_ary.h +418 -0
- data/ext/iodine/{base64.c → fio_base64.c} +33 -24
- data/ext/iodine/{base64.h → fio_base64.h} +6 -7
- data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
- data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
- data/ext/iodine/fio_hashmap.h +759 -0
- data/ext/iodine/fio_json_parser.h +651 -0
- data/ext/iodine/fio_llist.h +257 -0
- data/ext/iodine/fio_mem.c +672 -0
- data/ext/iodine/fio_mem.h +140 -0
- data/ext/iodine/fio_random.c +248 -0
- data/ext/iodine/{random.h → fio_random.h} +11 -14
- data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
- data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
- data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
- data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
- data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
- data/ext/iodine/fio_siphash.h +18 -0
- data/ext/iodine/fio_tmpfile.h +38 -0
- data/ext/iodine/fiobj.h +24 -7
- data/ext/iodine/fiobj4sock.h +23 -0
- data/ext/iodine/fiobj_ary.c +143 -226
- data/ext/iodine/fiobj_ary.h +17 -16
- data/ext/iodine/fiobj_data.c +1160 -0
- data/ext/iodine/fiobj_data.h +164 -0
- data/ext/iodine/fiobj_hash.c +298 -406
- data/ext/iodine/fiobj_hash.h +101 -54
- data/ext/iodine/fiobj_json.c +478 -601
- data/ext/iodine/fiobj_json.h +34 -9
- data/ext/iodine/fiobj_numbers.c +383 -51
- data/ext/iodine/fiobj_numbers.h +87 -11
- data/ext/iodine/fiobj_str.c +423 -184
- data/ext/iodine/fiobj_str.h +81 -32
- data/ext/iodine/fiobject.c +273 -522
- data/ext/iodine/fiobject.h +477 -112
- data/ext/iodine/http.c +2243 -83
- data/ext/iodine/http.h +842 -121
- data/ext/iodine/http1.c +810 -385
- data/ext/iodine/http1.h +16 -39
- data/ext/iodine/http1_parser.c +146 -74
- data/ext/iodine/http1_parser.h +15 -4
- data/ext/iodine/http_internal.c +1258 -0
- data/ext/iodine/http_internal.h +226 -0
- data/ext/iodine/http_mime_parser.h +341 -0
- data/ext/iodine/iodine.c +86 -68
- data/ext/iodine/iodine.h +26 -11
- data/ext/iodine/iodine_helpers.c +8 -7
- data/ext/iodine/iodine_http.c +487 -324
- data/ext/iodine/iodine_json.c +304 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_protocol.c +107 -45
- data/ext/iodine/iodine_pubsub.c +526 -225
- data/ext/iodine/iodine_pubsub.h +10 -0
- data/ext/iodine/iodine_websockets.c +268 -510
- data/ext/iodine/iodine_websockets.h +2 -4
- data/ext/iodine/pubsub.c +726 -432
- data/ext/iodine/pubsub.h +85 -103
- data/ext/iodine/rb-call.c +4 -4
- data/ext/iodine/rb-defer.c +46 -22
- data/ext/iodine/rb-fiobj2rb.h +117 -0
- data/ext/iodine/rb-rack-io.c +73 -238
- data/ext/iodine/rb-rack-io.h +2 -2
- data/ext/iodine/rb-registry.c +35 -93
- data/ext/iodine/rb-registry.h +1 -0
- data/ext/iodine/redis_engine.c +742 -304
- data/ext/iodine/redis_engine.h +42 -39
- data/ext/iodine/resp_parser.h +311 -0
- data/ext/iodine/sock.c +627 -490
- data/ext/iodine/sock.h +345 -297
- data/ext/iodine/spnlock.inc +15 -4
- data/ext/iodine/websocket_parser.h +16 -20
- data/ext/iodine/websockets.c +188 -257
- data/ext/iodine/websockets.h +24 -133
- data/lib/iodine.rb +52 -7
- data/lib/iodine/cli.rb +6 -24
- data/lib/iodine/json.rb +40 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +5 -3
- data/lib/rack/handler/iodine.rb +58 -13
- metadata +38 -48
- data/bin/ws-shootout +0 -107
- data/examples/broadcast.ru +0 -56
- data/ext/iodine/bscrypt-common.h +0 -116
- data/ext/iodine/bscrypt.h +0 -49
- data/ext/iodine/fio2resp.c +0 -60
- data/ext/iodine/fio2resp.h +0 -51
- data/ext/iodine/fio_dict.c +0 -446
- data/ext/iodine/fio_dict.h +0 -99
- data/ext/iodine/fio_hash_table.h +0 -370
- data/ext/iodine/fio_list.h +0 -111
- data/ext/iodine/fiobj_internal.h +0 -280
- data/ext/iodine/fiobj_primitives.c +0 -131
- data/ext/iodine/fiobj_primitives.h +0 -55
- data/ext/iodine/fiobj_sym.c +0 -135
- data/ext/iodine/fiobj_sym.h +0 -60
- data/ext/iodine/hex.c +0 -124
- data/ext/iodine/hex.h +0 -70
- data/ext/iodine/http1_request.c +0 -81
- data/ext/iodine/http1_request.h +0 -58
- data/ext/iodine/http1_response.c +0 -417
- data/ext/iodine/http1_response.h +0 -95
- data/ext/iodine/http_request.c +0 -111
- data/ext/iodine/http_request.h +0 -102
- data/ext/iodine/http_response.c +0 -1703
- data/ext/iodine/http_response.h +0 -250
- data/ext/iodine/misc.c +0 -182
- data/ext/iodine/misc.h +0 -74
- data/ext/iodine/random.c +0 -208
- data/ext/iodine/redis_connection.c +0 -278
- data/ext/iodine/redis_connection.h +0 -86
- data/ext/iodine/resp.c +0 -842
- data/ext/iodine/resp.h +0 -261
- data/ext/iodine/siphash.c +0 -154
- data/ext/iodine/siphash.h +0 -22
- data/ext/iodine/xor-crypt.c +0 -193
- data/ext/iodine/xor-crypt.h +0 -107
data/ext/iodine/http1.c
CHANGED
@@ -1,190 +1,594 @@
|
|
1
1
|
/*
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
Feel free to copy, use and enjoy according to the license provided.
|
2
|
+
Copyright: Boaz Segev, 2017
|
3
|
+
License: MIT
|
6
4
|
*/
|
7
5
|
#include "spnlock.inc"
|
8
6
|
|
9
|
-
#include "
|
7
|
+
#include "http1.h"
|
10
8
|
#include "http1_parser.h"
|
11
|
-
#include "
|
9
|
+
#include "http_internal.h"
|
10
|
+
#include "websockets.h"
|
11
|
+
|
12
|
+
// #include "fio_ary.h"
|
13
|
+
#include "fio_base64.h"
|
14
|
+
#include "fio_sha1.h"
|
15
|
+
#include "fiobj.h"
|
16
|
+
|
17
|
+
#include <assert.h>
|
18
|
+
#include <stddef.h>
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
/* Don't use `#define FIO_OVERRIDE_MALLOC 1`
|
21
|
+
* because protocol objects can have long life spans and fio_malloc is optimized
|
22
|
+
* for short life spans.
|
23
|
+
*/
|
24
|
+
#include "fio_mem.h"
|
17
25
|
|
18
|
-
char *HTTP11_Protocol_String = "facil_http/1.1_protocol";
|
19
26
|
/* *****************************************************************************
|
20
|
-
HTTP/1.1
|
27
|
+
The HTTP/1.1 Protocol Object
|
21
28
|
***************************************************************************** */
|
22
29
|
|
23
|
-
typedef struct
|
24
|
-
|
25
|
-
http_settings_s *settings;
|
30
|
+
typedef struct http1pr_s {
|
31
|
+
http_protocol_s p;
|
26
32
|
http1_parser_s parser;
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
http_s request;
|
34
|
+
uintptr_t buf_len;
|
35
|
+
uintptr_t max_header_size;
|
36
|
+
uintptr_t header_size;
|
37
|
+
uint8_t close;
|
38
|
+
uint8_t is_client;
|
39
|
+
uint8_t stop;
|
40
|
+
uint8_t buf[];
|
41
|
+
} http1pr_s;
|
42
|
+
|
43
|
+
struct http_vtable_s HTTP1_VTABLE; /* initialized later on */
|
44
|
+
|
45
|
+
/* *****************************************************************************
|
46
|
+
Internal Helpers
|
47
|
+
***************************************************************************** */
|
33
48
|
|
34
|
-
|
49
|
+
#define parser2http(x) \
|
50
|
+
((http1pr_s *)((uintptr_t)(x) - (uintptr_t)(&((http1pr_s *)0)->parser)))
|
51
|
+
|
52
|
+
inline static void h1_reset(http1pr_s *p) { p->header_size = 0; }
|
53
|
+
|
54
|
+
#define http1_pr2handle(pr) (((http1pr_s *)(pr))->request)
|
55
|
+
#define handle2pr(h) ((http1pr_s *)h->private_data.flag)
|
56
|
+
|
57
|
+
static fio_cstr_s http1pr_status2str(uintptr_t status);
|
58
|
+
|
59
|
+
/* cleanup an HTTP/1.1 handler object */
|
60
|
+
static inline void http1_after_finish(http_s *h) {
|
61
|
+
http1pr_s *p = handle2pr(h);
|
62
|
+
p->stop = p->stop & (~1UL);
|
63
|
+
if (h != &p->request) {
|
64
|
+
http_s_destroy(h, 0);
|
65
|
+
fio_free(h);
|
66
|
+
} else {
|
67
|
+
http_s_clear(h, p->p.settings->log);
|
68
|
+
}
|
69
|
+
if (p->close)
|
70
|
+
sock_close(p->p.uuid);
|
71
|
+
}
|
35
72
|
|
36
73
|
/* *****************************************************************************
|
37
|
-
HTTP/
|
74
|
+
HTTP Request / Response (Virtual) Functions
|
38
75
|
***************************************************************************** */
|
76
|
+
struct header_writer_s {
|
77
|
+
FIOBJ dest;
|
78
|
+
FIOBJ name;
|
79
|
+
FIOBJ value;
|
80
|
+
};
|
81
|
+
|
82
|
+
static int write_header(FIOBJ o, void *w_) {
|
83
|
+
struct header_writer_s *w = w_;
|
84
|
+
if (!o)
|
85
|
+
return 0;
|
86
|
+
if (fiobj_hash_key_in_loop()) {
|
87
|
+
w->name = fiobj_hash_key_in_loop();
|
88
|
+
}
|
89
|
+
if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) {
|
90
|
+
fiobj_each1(o, 0, write_header, w);
|
91
|
+
return 0;
|
92
|
+
}
|
93
|
+
fio_cstr_s name = fiobj_obj2cstr(w->name);
|
94
|
+
fio_cstr_s str = fiobj_obj2cstr(o);
|
95
|
+
if (!str.data)
|
96
|
+
return 0;
|
97
|
+
fiobj_str_capa_assert(w->dest,
|
98
|
+
fiobj_obj2cstr(w->dest).len + name.len + str.len + 5);
|
99
|
+
fiobj_str_write(w->dest, name.data, name.len);
|
100
|
+
fiobj_str_write(w->dest, ":", 1);
|
101
|
+
fiobj_str_write(w->dest, str.data, str.len);
|
102
|
+
fiobj_str_write(w->dest, "\r\n", 2);
|
103
|
+
return 0;
|
104
|
+
}
|
39
105
|
|
40
|
-
static
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
106
|
+
static FIOBJ headers2str(http_s *h, uintptr_t padding) {
|
107
|
+
if (!h->method && !!h->status_str)
|
108
|
+
return FIOBJ_INVALID;
|
109
|
+
|
110
|
+
static uintptr_t connection_hash;
|
111
|
+
if (!connection_hash)
|
112
|
+
connection_hash = fio_siphash("connection", 10);
|
113
|
+
|
114
|
+
struct header_writer_s w;
|
115
|
+
{
|
116
|
+
const uintptr_t header_length_guess =
|
117
|
+
fiobj_hash_count(h->private_data.out_headers) * 48;
|
118
|
+
w.dest = fiobj_str_buf(header_length_guess + padding);
|
119
|
+
}
|
120
|
+
http1pr_s *p = handle2pr(h);
|
121
|
+
|
122
|
+
if (p->is_client == 0) {
|
123
|
+
fio_cstr_s t = http1pr_status2str(h->status);
|
124
|
+
fiobj_str_write(w.dest, t.data, t.length);
|
125
|
+
FIOBJ tmp = fiobj_hash_get2(h->private_data.out_headers, connection_hash);
|
126
|
+
if (tmp) {
|
127
|
+
t = fiobj_obj2cstr(tmp);
|
128
|
+
if (t.data[0] == 'c' || t.data[0] == 'C')
|
129
|
+
p->close = 1;
|
130
|
+
} else {
|
131
|
+
tmp = fiobj_hash_get2(h->headers, connection_hash);
|
132
|
+
if (tmp) {
|
133
|
+
t = fiobj_obj2cstr(tmp);
|
134
|
+
if (!t.data || !t.len || t.data[0] == 'k' || t.data[0] == 'K')
|
135
|
+
fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
|
136
|
+
else {
|
137
|
+
fiobj_str_write(w.dest, "connection:close\r\n", 18);
|
138
|
+
p->close = 1;
|
139
|
+
}
|
140
|
+
} else {
|
141
|
+
t = fiobj_obj2cstr(h->version);
|
142
|
+
if (!p->close && t.len > 7 && t.data && t.data[5] == '1' &&
|
143
|
+
t.data[6] == '.' && t.data[7] == '1')
|
144
|
+
fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
|
145
|
+
else {
|
146
|
+
fiobj_str_write(w.dest, "connection:close\r\n", 18);
|
147
|
+
p->close = 1;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
} else {
|
152
|
+
if (h->method) {
|
153
|
+
fiobj_str_join(w.dest, h->method);
|
154
|
+
fiobj_str_write(w.dest, " ", 1);
|
155
|
+
} else {
|
156
|
+
fiobj_str_write(w.dest, "GET ", 4);
|
157
|
+
}
|
158
|
+
fiobj_str_join(w.dest, h->path);
|
159
|
+
if (h->query) {
|
160
|
+
fiobj_str_write(w.dest, "?", 1);
|
161
|
+
fiobj_str_join(w.dest, h->query);
|
162
|
+
}
|
163
|
+
fiobj_str_write(w.dest, " HTTP/1.1\r\n", 11);
|
164
|
+
/* make sure we have a host header? */
|
165
|
+
static uint64_t host_hash;
|
166
|
+
if (!host_hash)
|
167
|
+
host_hash = fio_siphash("host", 4);
|
168
|
+
FIOBJ tmp;
|
169
|
+
if (!fiobj_hash_get2(h->private_data.out_headers, host_hash) &&
|
170
|
+
(tmp = fiobj_hash_get2(h->headers, host_hash))) {
|
171
|
+
fiobj_str_write(w.dest, "host:", 5);
|
172
|
+
fiobj_str_join(w.dest, tmp);
|
173
|
+
fiobj_str_write(w.dest, "\r\n", 2);
|
174
|
+
}
|
175
|
+
if (!fiobj_hash_get2(h->private_data.out_headers, connection_hash))
|
176
|
+
fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
|
177
|
+
}
|
178
|
+
|
179
|
+
fiobj_each1(h->private_data.out_headers, 0, write_header, &w);
|
180
|
+
fiobj_str_write(w.dest, "\r\n", 2);
|
181
|
+
return w.dest;
|
60
182
|
}
|
61
183
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
}
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
if (
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
184
|
+
/** Should send existing headers and data */
|
185
|
+
static int http1_send_body(http_s *h, void *data, uintptr_t length) {
|
186
|
+
|
187
|
+
FIOBJ packet = headers2str(h, length);
|
188
|
+
if (!packet) {
|
189
|
+
http1_after_finish(h);
|
190
|
+
return -1;
|
191
|
+
}
|
192
|
+
fiobj_str_write(packet, data, length);
|
193
|
+
fiobj_send_free((handle2pr(h)->p.uuid), packet);
|
194
|
+
http1_after_finish(h);
|
195
|
+
return 0;
|
196
|
+
}
|
197
|
+
/** Should send existing headers and file */
|
198
|
+
static int http1_sendfile(http_s *h, int fd, uintptr_t length,
|
199
|
+
uintptr_t offset) {
|
200
|
+
FIOBJ packet = headers2str(h, 0);
|
201
|
+
if (!packet) {
|
202
|
+
close(fd);
|
203
|
+
http1_after_finish(h);
|
204
|
+
return -1;
|
205
|
+
}
|
206
|
+
if (length < HTTP_MAX_HEADER_LENGTH) {
|
207
|
+
/* optimize away small files */
|
208
|
+
fio_cstr_s s = fiobj_obj2cstr(packet);
|
209
|
+
fiobj_str_capa_assert(packet, s.len + length);
|
210
|
+
s = fiobj_obj2cstr(packet);
|
211
|
+
intptr_t i = pread(fd, s.data + s.len, length, offset);
|
212
|
+
if (i < 0) {
|
213
|
+
close(fd);
|
214
|
+
fiobj_send_free((handle2pr(h)->p.uuid), packet);
|
215
|
+
sock_close((handle2pr(h)->p.uuid));
|
216
|
+
return -1;
|
217
|
+
}
|
218
|
+
close(fd);
|
219
|
+
fiobj_str_resize(packet, s.len + i);
|
220
|
+
fiobj_send_free((handle2pr(h)->p.uuid), packet);
|
221
|
+
http1_after_finish(h);
|
222
|
+
return 0;
|
96
223
|
}
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
224
|
+
fiobj_send_free((handle2pr(h)->p.uuid), packet);
|
225
|
+
sock_sendfile((handle2pr(h)->p.uuid), fd, offset, length);
|
226
|
+
http1_after_finish(h);
|
227
|
+
return 0;
|
228
|
+
}
|
229
|
+
|
230
|
+
/** Should send existing headers or complete streaming */
|
231
|
+
static void htt1p_finish(http_s *h) {
|
232
|
+
FIOBJ packet = headers2str(h, 0);
|
233
|
+
if (packet)
|
234
|
+
fiobj_send_free((handle2pr(h)->p.uuid), packet);
|
235
|
+
else {
|
236
|
+
// fprintf(stderr, "WARNING: invalid call to `htt1p_finish`\n");
|
237
|
+
}
|
238
|
+
http1_after_finish(h);
|
239
|
+
}
|
240
|
+
/** Push for data - unsupported. */
|
241
|
+
static int http1_push_data(http_s *h, void *data, uintptr_t length,
|
242
|
+
FIOBJ mime_type) {
|
243
|
+
return -1;
|
244
|
+
(void)h;
|
245
|
+
(void)data;
|
246
|
+
(void)length;
|
247
|
+
(void)mime_type;
|
248
|
+
}
|
249
|
+
/** Push for files - unsupported. */
|
250
|
+
static int http1_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type) {
|
251
|
+
return -1;
|
252
|
+
(void)h;
|
253
|
+
(void)filename;
|
254
|
+
(void)mime_type;
|
255
|
+
}
|
256
|
+
|
257
|
+
/**
|
258
|
+
* Called befor a pause task,
|
259
|
+
*/
|
260
|
+
void http1_on_pause(http_s *h, http_protocol_s *pr) {
|
261
|
+
((http1pr_s *)pr)->stop = 1;
|
262
|
+
facil_quite(pr->uuid);
|
263
|
+
(void)h;
|
264
|
+
}
|
265
|
+
|
266
|
+
/**
|
267
|
+
* called after the resume task had completed.
|
268
|
+
*/
|
269
|
+
void http1_on_resume(http_s *h, http_protocol_s *pr) {
|
270
|
+
if (!((http1pr_s *)pr)->stop) {
|
271
|
+
facil_force_event(pr->uuid, FIO_EVENT_ON_DATA);
|
272
|
+
}
|
273
|
+
(void)h;
|
274
|
+
}
|
275
|
+
|
276
|
+
intptr_t http1_hijack(http_s *h, fio_cstr_s *leftover) {
|
277
|
+
if (leftover) {
|
278
|
+
intptr_t len =
|
279
|
+
handle2pr(h)->buf_len -
|
280
|
+
(intptr_t)(handle2pr(h)->parser.state.next - handle2pr(h)->buf);
|
281
|
+
if (len) {
|
282
|
+
*leftover =
|
283
|
+
(fio_cstr_s){.len = len, .bytes = handle2pr(h)->parser.state.next};
|
284
|
+
} else {
|
285
|
+
*leftover = (fio_cstr_s){.len = 0, .data = NULL};
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
handle2pr(h)->stop = 3;
|
290
|
+
intptr_t uuid = handle2pr(h)->p.uuid;
|
291
|
+
facil_attach(uuid, NULL);
|
292
|
+
return uuid;
|
102
293
|
}
|
103
294
|
|
104
295
|
/* *****************************************************************************
|
105
|
-
|
296
|
+
Websockets Upgrading
|
106
297
|
***************************************************************************** */
|
107
298
|
|
108
|
-
static void
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
299
|
+
static void http1_websocket_client_on_upgrade(http_s *h, char *proto,
|
300
|
+
size_t len) {
|
301
|
+
http1pr_s *p = handle2pr(h);
|
302
|
+
websocket_settings_s *args = h->udata;
|
303
|
+
args->http = h;
|
304
|
+
const intptr_t uuid = handle2pr(args->http)->p.uuid;
|
305
|
+
http_settings_s *set = handle2pr(args->http)->p.settings;
|
306
|
+
set->udata = NULL;
|
307
|
+
http_finish(args->http);
|
308
|
+
p->stop = 1;
|
309
|
+
websocket_attach(uuid, set, args, p->parser.state.next,
|
310
|
+
p->buf_len - (intptr_t)(p->parser.state.next - p->buf));
|
311
|
+
fio_free(args);
|
312
|
+
(void)proto;
|
313
|
+
(void)len;
|
314
|
+
}
|
315
|
+
static void http1_websocket_client_on_failed(http_s *h) {
|
316
|
+
websocket_settings_s *s = h->udata;
|
317
|
+
if (s->on_close)
|
318
|
+
s->on_close(0, s->udata);
|
319
|
+
fio_free(h->udata);
|
320
|
+
h->udata = http_settings(h)->udata = NULL;
|
321
|
+
}
|
322
|
+
static void http1_websocket_client_on_hangup(http_settings_s *settings) {
|
323
|
+
websocket_settings_s *s = settings->udata;
|
324
|
+
if (s) {
|
325
|
+
if (s->on_close)
|
326
|
+
s->on_close(0, settings->udata);
|
327
|
+
fio_free(settings->udata);
|
328
|
+
settings->udata = NULL;
|
121
329
|
}
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
static
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
if (!
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
330
|
+
}
|
331
|
+
|
332
|
+
static int http1_http2websocket_server(websocket_settings_s *args) {
|
333
|
+
// A static data used for all websocket connections.
|
334
|
+
static char ws_key_accpt_str[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
335
|
+
static uintptr_t sec_version = 0;
|
336
|
+
static uintptr_t sec_key = 0;
|
337
|
+
if (!sec_version)
|
338
|
+
sec_version = fio_siphash("sec-websocket-version", 21);
|
339
|
+
if (!sec_key)
|
340
|
+
sec_key = fio_siphash("sec-websocket-key", 17);
|
341
|
+
|
342
|
+
FIOBJ tmp = fiobj_hash_get2(args->http->headers, sec_version);
|
343
|
+
if (!tmp)
|
344
|
+
goto bad_request;
|
345
|
+
fio_cstr_s stmp = fiobj_obj2cstr(tmp);
|
346
|
+
if (stmp.length != 2 || stmp.data[0] != '1' || stmp.data[1] != '3')
|
347
|
+
goto bad_request;
|
348
|
+
|
349
|
+
tmp = fiobj_hash_get2(args->http->headers, sec_key);
|
350
|
+
if (!tmp)
|
351
|
+
goto bad_request;
|
352
|
+
stmp = fiobj_obj2cstr(tmp);
|
353
|
+
|
354
|
+
sha1_s sha1 = fio_sha1_init();
|
355
|
+
fio_sha1_write(&sha1, stmp.data, stmp.len);
|
356
|
+
fio_sha1_write(&sha1, ws_key_accpt_str, sizeof(ws_key_accpt_str) - 1);
|
357
|
+
tmp = fiobj_str_buf(32);
|
358
|
+
stmp = fiobj_obj2cstr(tmp);
|
359
|
+
fiobj_str_resize(tmp,
|
360
|
+
fio_base64_encode(stmp.data, fio_sha1_result(&sha1), 20));
|
361
|
+
http_set_header(args->http, HTTP_HEADER_CONNECTION,
|
362
|
+
fiobj_dup(HTTP_HVALUE_WS_UPGRADE));
|
363
|
+
http_set_header(args->http, HTTP_HEADER_UPGRADE,
|
364
|
+
fiobj_dup(HTTP_HVALUE_WEBSOCKET));
|
365
|
+
http_set_header(args->http, HTTP_HEADER_WS_SEC_KEY, tmp);
|
366
|
+
args->http->status = 101;
|
367
|
+
http1pr_s *pr = handle2pr(args->http);
|
368
|
+
const intptr_t uuid = handle2pr(args->http)->p.uuid;
|
369
|
+
http_settings_s *set = handle2pr(args->http)->p.settings;
|
370
|
+
http_finish(args->http);
|
371
|
+
pr->stop = 1;
|
372
|
+
websocket_attach(uuid, set, args, pr->parser.state.next,
|
373
|
+
pr->buf_len - (intptr_t)(pr->parser.state.next - pr->buf));
|
374
|
+
return 0;
|
375
|
+
bad_request:
|
376
|
+
http_send_error(args->http, 400);
|
377
|
+
if (args->on_close)
|
378
|
+
args->on_close(0, args->udata);
|
379
|
+
return -1;
|
380
|
+
}
|
381
|
+
|
382
|
+
static int http1_http2websocket_client(websocket_settings_s *args) {
|
383
|
+
http1pr_s *p = handle2pr(args->http);
|
384
|
+
/* We're done with the HTTP stage, so we call the `on_finish` */
|
385
|
+
if (p->p.settings->on_finish)
|
386
|
+
p->p.settings->on_finish(p->p.settings);
|
387
|
+
/* Copy the Websocket setting arguments to the HTTP settings `udata` */
|
388
|
+
p->p.settings->udata = fio_malloc(sizeof(*args));
|
389
|
+
((websocket_settings_s *)(p->p.settings->udata))[0] = *args;
|
390
|
+
/* Set callbacks */
|
391
|
+
p->p.settings->on_finish = http1_websocket_client_on_hangup; /* unknown */
|
392
|
+
p->p.settings->on_upgrade = http1_websocket_client_on_upgrade; /* sucess */
|
393
|
+
p->p.settings->on_response = http1_websocket_client_on_failed; /* failed */
|
394
|
+
p->p.settings->on_request = http1_websocket_client_on_failed; /* failed */
|
395
|
+
/* Set headers */
|
396
|
+
http_set_header(args->http, HTTP_HEADER_CONNECTION,
|
397
|
+
fiobj_dup(HTTP_HVALUE_WS_UPGRADE));
|
398
|
+
http_set_header(args->http, HTTP_HEADER_UPGRADE,
|
399
|
+
fiobj_dup(HTTP_HVALUE_WEBSOCKET));
|
400
|
+
http_set_header(args->http, HTTP_HVALUE_WS_SEC_VERSION,
|
401
|
+
fiobj_dup(HTTP_HVALUE_WS_VERSION));
|
402
|
+
|
403
|
+
/* we don't set the Origin header since we're not a browser... should we? */
|
404
|
+
// http_set_header(
|
405
|
+
// args->http, HTTP_HEADER_ORIGIN,
|
406
|
+
// fiobj_dup(fiobj_hash_get2(args->http->private_data.out_headers,
|
407
|
+
// fiobj_obj2hash(HTTP_HEADER_HOST))));
|
408
|
+
|
409
|
+
/* create nonce */
|
410
|
+
uint64_t key[2]; /* 16 bytes */
|
411
|
+
key[0] = (uintptr_t)args->http ^ (uint64_t)facil_last_tick().tv_sec;
|
412
|
+
key[1] = (uintptr_t)args->udata ^ (uint64_t)facil_last_tick().tv_nsec;
|
413
|
+
FIOBJ encoded = fiobj_str_buf(26); /* we need 24 really. */
|
414
|
+
fio_cstr_s tmp = fiobj_obj2cstr(encoded);
|
415
|
+
tmp.len = fio_base64_encode(tmp.data, (char *)key, 16);
|
416
|
+
fiobj_str_resize(encoded, tmp.len);
|
417
|
+
http_set_header(args->http, HTTP_HEADER_WS_SEC_CLIENT_KEY, encoded);
|
418
|
+
http_finish(args->http);
|
419
|
+
return 0;
|
420
|
+
}
|
421
|
+
|
422
|
+
static int http1_http2websocket(websocket_settings_s *args) {
|
423
|
+
assert(args->http);
|
424
|
+
http1pr_s *p = handle2pr(args->http);
|
425
|
+
|
426
|
+
if (p->is_client == 0) {
|
427
|
+
return http1_http2websocket_server(args);
|
138
428
|
}
|
429
|
+
return http1_http2websocket_client(args);
|
139
430
|
}
|
140
431
|
|
141
432
|
/* *****************************************************************************
|
142
|
-
|
433
|
+
EventSource Support (SSE)
|
143
434
|
***************************************************************************** */
|
144
435
|
|
145
|
-
#
|
436
|
+
#undef http_upgrade2sse
|
146
437
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
438
|
+
typedef struct {
|
439
|
+
protocol_s p;
|
440
|
+
http_sse_internal_s *sse;
|
441
|
+
} http1_sse_protocol_s;
|
442
|
+
|
443
|
+
static void http1_sse_on_ready(intptr_t uuid, protocol_s *p_) {
|
444
|
+
http1_sse_protocol_s *p = (http1_sse_protocol_s *)p_;
|
445
|
+
if (p->sse->sse.on_ready)
|
446
|
+
p->sse->sse.on_ready(&p->sse->sse);
|
447
|
+
(void)uuid;
|
448
|
+
}
|
449
|
+
static void http1_sse_on_shutdown(intptr_t uuid, protocol_s *p_) {
|
450
|
+
http1_sse_protocol_s *p = (http1_sse_protocol_s *)p_;
|
451
|
+
if (p->sse->sse.on_shutdown)
|
452
|
+
p->sse->sse.on_shutdown(&p->sse->sse);
|
453
|
+
(void)uuid;
|
454
|
+
}
|
455
|
+
static void http1_sse_on_close(intptr_t uuid, protocol_s *p_) {
|
456
|
+
http1_sse_protocol_s *p = (http1_sse_protocol_s *)p_;
|
457
|
+
if (p->sse->sse.on_close)
|
458
|
+
p->sse->sse.on_close(&p->sse->sse);
|
459
|
+
http_sse_destroy(p->sse);
|
460
|
+
free(p);
|
461
|
+
(void)uuid;
|
462
|
+
}
|
463
|
+
static void http1_sse_ping(intptr_t uuid, protocol_s *p_) {
|
464
|
+
sock_write2(.uuid = uuid, .buffer = ": ping\n\n", .length = 8,
|
465
|
+
.dealloc = SOCK_DEALLOC_NOOP);
|
466
|
+
(void)p_;
|
467
|
+
}
|
468
|
+
|
469
|
+
/**
|
470
|
+
* Upgrades an HTTP connection to an EventSource (SSE) connection.
|
471
|
+
*
|
472
|
+
* Thie `http_s` handle will be invalid after this call.
|
473
|
+
*
|
474
|
+
* On HTTP/1.1 connections, this will preclude future requests using the same
|
475
|
+
* connection.
|
476
|
+
*/
|
477
|
+
static int http1_upgrade2sse(http_s *h, http_sse_s *sse) {
|
478
|
+
const intptr_t uuid = handle2pr(h)->p.uuid;
|
479
|
+
/* send response */
|
480
|
+
h->status = 200;
|
481
|
+
http_set_header(h, HTTP_HEADER_CONTENT_TYPE, fiobj_dup(HTTP_HVALUE_SSE_MIME));
|
482
|
+
http_set_header(h, HTTP_HEADER_CACHE_CONTROL,
|
483
|
+
fiobj_dup(HTTP_HVALUE_NO_CACHE));
|
484
|
+
http_set_header(h, HTTP_HEADER_CONTENT_ENCODING,
|
485
|
+
fiobj_str_new("identity", 8));
|
486
|
+
handle2pr(h)->stop = 1;
|
487
|
+
htt1p_finish(h); /* avoid the enforced content length in http_finish */
|
488
|
+
|
489
|
+
/* switch protocol to SSE */
|
490
|
+
http1_sse_protocol_s *sse_pr = malloc(sizeof(*sse_pr));
|
491
|
+
if (!sse_pr)
|
492
|
+
goto failed;
|
493
|
+
*sse_pr = (http1_sse_protocol_s){
|
494
|
+
.p =
|
495
|
+
{
|
496
|
+
.service = "http/1.1 internal SSE",
|
497
|
+
.on_ready = http1_sse_on_ready,
|
498
|
+
.on_shutdown = http1_sse_on_shutdown,
|
499
|
+
.on_close = http1_sse_on_close,
|
500
|
+
.ping = http1_sse_ping,
|
501
|
+
},
|
502
|
+
.sse = malloc(sizeof(*(sse_pr->sse))),
|
503
|
+
};
|
504
|
+
|
505
|
+
if (!sse_pr->sse)
|
506
|
+
goto failed;
|
507
|
+
|
508
|
+
http_sse_init(sse_pr->sse, uuid, &HTTP1_VTABLE, sse);
|
509
|
+
|
510
|
+
if (facil_attach(uuid, &sse_pr->p))
|
151
511
|
return -1;
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
http_settings_s *settings = pr->settings;
|
157
|
-
pr->request.request.settings = settings;
|
158
|
-
// make sure udata to NULL, making it available for the user
|
159
|
-
pr->request.request.udata = NULL;
|
160
|
-
// static file service or call request callback
|
161
|
-
if (pr->request.request.upgrade || settings->public_folder == NULL ||
|
162
|
-
http_response_sendfile2(
|
163
|
-
NULL, &pr->request.request, settings->public_folder,
|
164
|
-
settings->public_folder_length, pr->request.request.path,
|
165
|
-
pr->request.request.path_len, settings->log_static)) {
|
166
|
-
pr->on_request(&pr->request.request);
|
167
|
-
}
|
168
|
-
pr->refresh = 1;
|
512
|
+
|
513
|
+
if (sse->on_open)
|
514
|
+
sse->on_open(&sse_pr->sse->sse);
|
515
|
+
|
169
516
|
return 0;
|
170
|
-
|
171
|
-
|
172
|
-
|
517
|
+
|
518
|
+
failed:
|
519
|
+
sock_close(handle2pr(h)->p.uuid);
|
520
|
+
if (sse->on_close)
|
521
|
+
sse->on_close(sse);
|
173
522
|
return -1;
|
523
|
+
(void)sse;
|
524
|
+
}
|
525
|
+
|
526
|
+
#undef http_sse_write
|
527
|
+
/**
|
528
|
+
* Writes data to an EventSource (SSE) connection.
|
529
|
+
*
|
530
|
+
* See the {struct http_sse_write_args} for possible named arguments.
|
531
|
+
*/
|
532
|
+
static int http1_sse_write(http_sse_s *sse, FIOBJ str) {
|
533
|
+
return fiobj_send_free(((http_sse_internal_s *)sse)->uuid, str);
|
534
|
+
}
|
535
|
+
|
536
|
+
/**
|
537
|
+
* Closes an EventSource (SSE) connection.
|
538
|
+
*/
|
539
|
+
static int http1_sse_close(http_sse_s *sse) {
|
540
|
+
sock_close(((http_sse_internal_s *)sse)->uuid);
|
541
|
+
return 0;
|
542
|
+
}
|
543
|
+
/* *****************************************************************************
|
544
|
+
Virtual Table Decleration
|
545
|
+
***************************************************************************** */
|
546
|
+
|
547
|
+
struct http_vtable_s HTTP1_VTABLE = {
|
548
|
+
.http_send_body = http1_send_body,
|
549
|
+
.http_sendfile = http1_sendfile,
|
550
|
+
.http_finish = htt1p_finish,
|
551
|
+
.http_push_data = http1_push_data,
|
552
|
+
.http_push_file = http1_push_file,
|
553
|
+
.http_on_pause = http1_on_pause,
|
554
|
+
.http_on_resume = http1_on_resume,
|
555
|
+
.http_hijack = http1_hijack,
|
556
|
+
.http2websocket = http1_http2websocket,
|
557
|
+
.http_upgrade2sse = http1_upgrade2sse,
|
558
|
+
.http_sse_write = http1_sse_write,
|
559
|
+
.http_sse_close = http1_sse_close,
|
560
|
+
};
|
561
|
+
|
562
|
+
void *http1_vtable(void) { return (void *)&HTTP1_VTABLE; }
|
563
|
+
|
564
|
+
/* *****************************************************************************
|
565
|
+
Parser Callbacks
|
566
|
+
***************************************************************************** */
|
567
|
+
|
568
|
+
/** called when a request was received. */
|
569
|
+
static int http1_on_request(http1_parser_s *parser) {
|
570
|
+
http1pr_s *p = parser2http(parser);
|
571
|
+
http_on_request_handler______internal(&http1_pr2handle(p), p->p.settings);
|
572
|
+
if (p->request.method && !p->stop)
|
573
|
+
http_finish(&p->request);
|
574
|
+
h1_reset(p);
|
575
|
+
return 0;
|
174
576
|
}
|
175
577
|
/** called when a response was received. */
|
176
578
|
static int http1_on_response(http1_parser_s *parser) {
|
177
|
-
|
178
|
-
(
|
579
|
+
http1pr_s *p = parser2http(parser);
|
580
|
+
http_on_response_handler______internal(&http1_pr2handle(p), p->p.settings);
|
581
|
+
if (p->request.status_str && !p->stop)
|
582
|
+
http_finish(&p->request);
|
583
|
+
h1_reset(p);
|
584
|
+
return 0;
|
179
585
|
}
|
180
586
|
/** called when a request method is parsed. */
|
181
587
|
static int http1_on_method(http1_parser_s *parser, char *method,
|
182
588
|
size_t method_len) {
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
pr->request.request.method = method;
|
187
|
-
pr->request.request.method_len = method_len;
|
589
|
+
http1_pr2handle(parser2http(parser)).method =
|
590
|
+
fiobj_str_new(method, method_len);
|
591
|
+
parser2http(parser)->header_size += method_len;
|
188
592
|
return 0;
|
189
593
|
}
|
190
594
|
|
@@ -192,297 +596,318 @@ static int http1_on_method(http1_parser_s *parser, char *method,
|
|
192
596
|
* without the prefixed numerical status indicator.*/
|
193
597
|
static int http1_on_status(http1_parser_s *parser, size_t status,
|
194
598
|
char *status_str, size_t len) {
|
195
|
-
|
196
|
-
|
197
|
-
(
|
198
|
-
(
|
199
|
-
|
599
|
+
http1_pr2handle(parser2http(parser)).status_str =
|
600
|
+
fiobj_str_new(status_str, len);
|
601
|
+
http1_pr2handle(parser2http(parser)).status = status;
|
602
|
+
parser2http(parser)->header_size += len;
|
603
|
+
return 0;
|
200
604
|
}
|
201
605
|
|
202
606
|
/** called when a request path (excluding query) is parsed. */
|
203
|
-
static int http1_on_path(http1_parser_s *parser, char *path, size_t
|
204
|
-
|
205
|
-
|
206
|
-
return -1;
|
207
|
-
pr->request.request.path = path;
|
208
|
-
pr->request.request.path_len = path_len;
|
607
|
+
static int http1_on_path(http1_parser_s *parser, char *path, size_t len) {
|
608
|
+
http1_pr2handle(parser2http(parser)).path = fiobj_str_new(path, len);
|
609
|
+
parser2http(parser)->header_size += len;
|
209
610
|
return 0;
|
210
611
|
}
|
211
612
|
|
212
613
|
/** called when a request path (excluding query) is parsed. */
|
213
|
-
static int http1_on_query(http1_parser_s *parser, char *query,
|
214
|
-
|
215
|
-
|
216
|
-
if (!pr)
|
217
|
-
return -1;
|
218
|
-
pr->request.request.query = query;
|
219
|
-
pr->request.request.query_len = query_len;
|
614
|
+
static int http1_on_query(http1_parser_s *parser, char *query, size_t len) {
|
615
|
+
http1_pr2handle(parser2http(parser)).query = fiobj_str_new(query, len);
|
616
|
+
parser2http(parser)->header_size += len;
|
220
617
|
return 0;
|
221
618
|
}
|
222
|
-
|
223
619
|
/** called when a the HTTP/1.x version is parsed. */
|
224
620
|
static int http1_on_http_version(http1_parser_s *parser, char *version,
|
225
621
|
size_t len) {
|
226
|
-
|
227
|
-
|
228
|
-
return -1;
|
229
|
-
pr->request.request.version = version;
|
230
|
-
pr->request.request.version_len = len;
|
622
|
+
http1_pr2handle(parser2http(parser)).version = fiobj_str_new(version, len);
|
623
|
+
parser2http(parser)->header_size += len;
|
231
624
|
return 0;
|
232
625
|
}
|
233
|
-
|
234
626
|
/** called when a header is parsed. */
|
235
627
|
static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len,
|
236
628
|
char *data, size_t data_len) {
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
pr->request.request.host_len = data_len;
|
248
|
-
} else if (name_len == 12 && *((uint32_t *)name) == *((uint32_t *)"cont") &&
|
249
|
-
*((uint64_t *)(name + 4)) == *((uint64_t *)"ent-type")) {
|
250
|
-
pr->request.request.content_type = data;
|
251
|
-
pr->request.request.content_type_len = data_len;
|
252
|
-
} else if (name_len == 7 && *((uint64_t *)name) == *((uint64_t *)"upgrade")) {
|
253
|
-
pr->request.request.upgrade = data;
|
254
|
-
pr->request.request.upgrade_len = data_len;
|
255
|
-
} else if (name_len == 10 && *((uint32_t *)name) == *((uint32_t *)"conn") &&
|
256
|
-
*((uint64_t *)(name + 2)) == *((uint64_t *)"nnection")) {
|
257
|
-
pr->request.request.connection = data;
|
258
|
-
pr->request.request.connection_len = data_len;
|
629
|
+
FIOBJ sym;
|
630
|
+
FIOBJ obj;
|
631
|
+
if (!http1_pr2handle(parser2http(parser)).headers) {
|
632
|
+
fprintf(stderr,
|
633
|
+
"ERROR: (http1 parse ordering error) missing HashMap for header "
|
634
|
+
"%s: %s\n",
|
635
|
+
name, data);
|
636
|
+
http_send_error2(500, parser2http(parser)->p.uuid,
|
637
|
+
parser2http(parser)->p.settings);
|
638
|
+
return -1;
|
259
639
|
}
|
260
|
-
|
261
|
-
if (
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
} else if (name_len == 10 &&
|
272
|
-
HEADER_NAME_IS_EQ(name, "connection", name_len)) {
|
273
|
-
pr->request.request.connection = data;
|
274
|
-
pr->request.request.connection_len = data_len;
|
640
|
+
parser2http(parser)->header_size += name_len + data_len;
|
641
|
+
if (parser2http(parser)->header_size >=
|
642
|
+
parser2http(parser)->max_header_size ||
|
643
|
+
fiobj_hash_count(http1_pr2handle(parser2http(parser)).headers) >
|
644
|
+
HTTP_MAX_HEADER_COUNT) {
|
645
|
+
if (parser2http(parser)->p.settings->log) {
|
646
|
+
fprintf(stderr,
|
647
|
+
"WARNING: (http security alert) header flood detected.\n");
|
648
|
+
}
|
649
|
+
http_send_error(&http1_pr2handle(parser2http(parser)), 413);
|
650
|
+
return -1;
|
275
651
|
}
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
pr->request.headers[pr->request.header_pos].data_len = data_len;
|
281
|
-
pr->request.header_pos++;
|
282
|
-
pr->request.request.headers_count++;
|
652
|
+
sym = fiobj_str_new(name, name_len);
|
653
|
+
obj = fiobj_str_new(data, data_len);
|
654
|
+
set_header_add(http1_pr2handle(parser2http(parser)).headers, sym, obj);
|
655
|
+
fiobj_free(sym);
|
283
656
|
return 0;
|
284
|
-
too_big:
|
285
|
-
/* handle oversized headers */
|
286
|
-
err_too_big(pr);
|
287
|
-
return -1;
|
288
657
|
}
|
289
|
-
|
290
658
|
/** called when a body chunk is parsed. */
|
291
659
|
static int http1_on_body_chunk(http1_parser_s *parser, char *data,
|
292
660
|
size_t data_len) {
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
661
|
+
if (parser->state.content_length >
|
662
|
+
(ssize_t)parser2http(parser)->p.settings->max_body_size ||
|
663
|
+
parser->state.read >
|
664
|
+
(ssize_t)parser2http(parser)->p.settings->max_body_size) {
|
665
|
+
http_send_error(&http1_pr2handle(parser2http(parser)), 413);
|
298
666
|
return -1; /* test every time, in case of chunked data */
|
667
|
+
}
|
299
668
|
if (!parser->state.read) {
|
300
669
|
if (parser->state.content_length > 0 &&
|
301
|
-
|
302
|
-
|
303
|
-
pr->request.request.body_str = data;
|
670
|
+
parser->state.content_length <= HTTP_MAX_HEADER_LENGTH) {
|
671
|
+
http1_pr2handle(parser2http(parser)).body = fiobj_data_newstr();
|
304
672
|
} else {
|
305
|
-
|
306
|
-
#ifdef P_tmpdir
|
307
|
-
#if defined(__linux__) /* linux doesn't end with a divider */
|
308
|
-
char template[] = P_tmpdir "/http_request_body_XXXXXXXX";
|
309
|
-
#else
|
310
|
-
char template[] = P_tmpdir "http_request_body_XXXXXXXX";
|
311
|
-
#endif
|
312
|
-
#else
|
313
|
-
char template[] = "/tmp/http_request_body_XXXXXXXX";
|
314
|
-
#endif
|
315
|
-
pr->request.request.body_file = mkstemp(template);
|
316
|
-
if (pr->request.request.body_file == -1)
|
317
|
-
return -1;
|
673
|
+
http1_pr2handle(parser2http(parser)).body = fiobj_data_newtmpfile();
|
318
674
|
}
|
319
675
|
}
|
320
|
-
|
321
|
-
if (write(pr->request.request.body_file, data, data_len) !=
|
322
|
-
(ssize_t)data_len)
|
323
|
-
return -1;
|
324
|
-
} else {
|
325
|
-
/* nothing to do... the parser and `on_data` are doing all the work */
|
326
|
-
}
|
676
|
+
fiobj_data_write(http1_pr2handle(parser2http(parser)).body, data, data_len);
|
327
677
|
return 0;
|
328
678
|
}
|
329
679
|
|
330
680
|
/** called when a protocol error occured. */
|
331
681
|
static int http1_on_error(http1_parser_s *parser) {
|
332
|
-
|
333
|
-
|
334
|
-
return -1;
|
335
|
-
sock_close(pr->request.request.fd);
|
336
|
-
http1_request_clear(&pr->request.request);
|
337
|
-
return 0;
|
682
|
+
sock_close(parser2http(parser)->p.uuid);
|
683
|
+
return -1;
|
338
684
|
}
|
339
685
|
|
340
686
|
/* *****************************************************************************
|
341
|
-
|
342
|
-
*****************************************************************************
|
687
|
+
Connection Callbacks
|
688
|
+
*****************************************************************************
|
689
|
+
*/
|
343
690
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
691
|
+
/**
|
692
|
+
* A string to identify the protocol's service (i.e. "http").
|
693
|
+
*
|
694
|
+
* The string should be a global constant, only a pointer comparison will be
|
695
|
+
* used (not `strcmp`).
|
696
|
+
*/
|
697
|
+
static const char *HTTP1_SERVICE_STR = "http1_protocol_facil_io";
|
698
|
+
|
699
|
+
static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) {
|
700
|
+
ssize_t i = 0;
|
701
|
+
size_t org_len = p->buf_len;
|
702
|
+
int pipeline_limit = 8;
|
703
|
+
do {
|
704
|
+
i = http1_fio_parser(.parser = &p->parser,
|
705
|
+
.buffer = p->buf + (org_len - p->buf_len),
|
706
|
+
.length = p->buf_len, .on_request = http1_on_request,
|
707
|
+
.on_response = http1_on_response,
|
708
|
+
.on_method = http1_on_method,
|
709
|
+
.on_status = http1_on_status, .on_path = http1_on_path,
|
710
|
+
.on_query = http1_on_query,
|
711
|
+
.on_http_version = http1_on_http_version,
|
712
|
+
.on_header = http1_on_header,
|
713
|
+
.on_body_chunk = http1_on_body_chunk,
|
714
|
+
.on_error = http1_on_error);
|
715
|
+
p->buf_len -= i;
|
716
|
+
--pipeline_limit;
|
717
|
+
} while (i && p->buf_len && pipeline_limit && !p->stop);
|
718
|
+
|
719
|
+
if (p->buf_len && org_len != p->buf_len) {
|
720
|
+
memmove(p->buf, p->buf + (org_len - p->buf_len), p->buf_len);
|
721
|
+
}
|
722
|
+
|
723
|
+
if (p->buf_len == HTTP_MAX_HEADER_LENGTH) {
|
724
|
+
/* no room to read... parser not consuming data */
|
725
|
+
if (p->request.method)
|
726
|
+
http_send_error(&p->request, 413);
|
727
|
+
else {
|
728
|
+
p->request.method = fiobj_str_tmp();
|
729
|
+
http_send_error(&p->request, 413);
|
368
730
|
}
|
369
|
-
buffer = buff;
|
370
|
-
tmp = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE);
|
371
|
-
if (tmp > 0) {
|
372
|
-
request->buffer_pos += tmp;
|
373
|
-
pr->len += tmp;
|
374
|
-
} else
|
375
|
-
tmp = 0;
|
376
731
|
}
|
377
732
|
|
378
|
-
if (
|
733
|
+
if (!pipeline_limit) {
|
734
|
+
facil_force_event(uuid, FIO_EVENT_ON_DATA);
|
735
|
+
}
|
736
|
+
}
|
737
|
+
|
738
|
+
/** called when a data is available, but will not run concurrently */
|
739
|
+
static void http1_on_data(intptr_t uuid, protocol_s *protocol) {
|
740
|
+
http1pr_s *p = (http1pr_s *)protocol;
|
741
|
+
if (p->stop) {
|
742
|
+
facil_quite(uuid);
|
379
743
|
return;
|
744
|
+
}
|
745
|
+
ssize_t i = 0;
|
746
|
+
if (HTTP_MAX_HEADER_LENGTH - p->buf_len)
|
747
|
+
i = sock_read(uuid, p->buf + p->buf_len,
|
748
|
+
HTTP_MAX_HEADER_LENGTH - p->buf_len);
|
749
|
+
if (i > 0) {
|
750
|
+
p->buf_len += i;
|
751
|
+
}
|
752
|
+
http1_consume_data(uuid, p);
|
753
|
+
}
|
380
754
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
http1_request_clear(&request->request);
|
406
|
-
pr->len = 0;
|
407
|
-
} else {
|
408
|
-
pr->len = pr->len - consumed;
|
755
|
+
/** called when the connection was closed, but will not run concurrently */
|
756
|
+
static void http1_on_close(intptr_t uuid, protocol_s *protocol) {
|
757
|
+
http1_destroy(protocol);
|
758
|
+
(void)uuid;
|
759
|
+
}
|
760
|
+
|
761
|
+
/** called when a data is available for the first time */
|
762
|
+
static void http1_on_data_first_time(intptr_t uuid, protocol_s *protocol) {
|
763
|
+
http1pr_s *p = (http1pr_s *)protocol;
|
764
|
+
ssize_t i;
|
765
|
+
|
766
|
+
i = sock_read(uuid, p->buf + p->buf_len, HTTP_MAX_HEADER_LENGTH - p->buf_len);
|
767
|
+
|
768
|
+
if (i <= 0)
|
769
|
+
return;
|
770
|
+
p->buf_len += i;
|
771
|
+
|
772
|
+
/* ensure future reads skip this first time HTTP/2.0 test */
|
773
|
+
p->p.protocol.on_data = http1_on_data;
|
774
|
+
if (i >= 24 && !memcmp(p->buf, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24)) {
|
775
|
+
fprintf(stderr,
|
776
|
+
"ERROR: unsupported HTTP/2 attempeted using prior knowledge.\n");
|
777
|
+
sock_close(uuid);
|
778
|
+
return;
|
409
779
|
}
|
410
|
-
|
411
|
-
|
780
|
+
|
781
|
+
/* Finish handling the same way as the normal `on_data` */
|
782
|
+
http1_consume_data(uuid, p);
|
412
783
|
}
|
413
784
|
|
414
785
|
/* *****************************************************************************
|
415
|
-
|
786
|
+
Public API
|
787
|
+
*****************************************************************************
|
416
788
|
*/
|
417
789
|
|
418
|
-
/**
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
protocol_s *http1_on_open(intptr_t fd, http_settings_s *settings) {
|
424
|
-
if (sock_uuid2fd(fd) >= (sock_max_capacity() - HTTP_BUSY_UNLESS_HAS_FDS))
|
425
|
-
goto is_busy;
|
426
|
-
http1_protocol_s *pr = http1_alloc();
|
427
|
-
pr->request.request.fd = fd;
|
428
|
-
pr->settings = settings;
|
429
|
-
pr->on_request = settings->on_request;
|
430
|
-
facil_set_timeout(fd, pr->settings->timeout);
|
431
|
-
return (protocol_s *)pr;
|
432
|
-
|
433
|
-
is_busy:
|
434
|
-
if (settings->public_folder && settings->public_folder_length) {
|
435
|
-
size_t p_len = settings->public_folder_length;
|
436
|
-
struct stat file_data = {.st_mode = 0};
|
437
|
-
char fname[p_len + 8 + 1];
|
438
|
-
memcpy(fname, settings->public_folder, p_len);
|
439
|
-
if (settings->public_folder[p_len - 1] == '/' ||
|
440
|
-
settings->public_folder[p_len - 1] == '\\')
|
441
|
-
p_len--;
|
442
|
-
memcpy(fname + p_len, "/503.html", 9);
|
443
|
-
p_len += 9;
|
444
|
-
if (stat(fname, &file_data))
|
445
|
-
goto busy_no_file;
|
446
|
-
// check that we have a file and not something else
|
447
|
-
if (!S_ISREG(file_data.st_mode) && !S_ISLNK(file_data.st_mode))
|
448
|
-
goto busy_no_file;
|
449
|
-
int file = open(fname, O_RDONLY);
|
450
|
-
if (file == -1)
|
451
|
-
goto busy_no_file;
|
452
|
-
sock_buffer_s *buffer;
|
453
|
-
buffer = sock_buffer_checkout();
|
454
|
-
memcpy(buffer->buf,
|
455
|
-
"HTTP/1.1 503 Service Unavailable\r\n"
|
456
|
-
"Content-Type: text/html\r\n"
|
457
|
-
"Connection: close\r\n"
|
458
|
-
"Content-Length: ",
|
459
|
-
94);
|
460
|
-
p_len = 94 + http_ul2a((char *)buffer->buf + 94, file_data.st_size);
|
461
|
-
memcpy(buffer->buf + p_len, "\r\n\r\n", 4);
|
462
|
-
p_len += 4;
|
463
|
-
if ((off_t)(BUFFER_PACKET_SIZE - p_len) > file_data.st_size) {
|
464
|
-
if (read(file, buffer->buf + p_len, file_data.st_size) < 0) {
|
465
|
-
close(file);
|
466
|
-
sock_buffer_free(buffer);
|
467
|
-
goto busy_no_file;
|
468
|
-
}
|
469
|
-
close(file);
|
470
|
-
buffer->len = p_len + file_data.st_size;
|
471
|
-
sock_buffer_send(fd, buffer);
|
472
|
-
} else {
|
473
|
-
buffer->len = p_len;
|
474
|
-
sock_buffer_send(fd, buffer);
|
475
|
-
sock_sendfile(fd, file, 0, file_data.st_size);
|
476
|
-
sock_close(fd);
|
477
|
-
}
|
790
|
+
/** Creates an HTTP1 protocol object and handles any unread data in the buffer
|
791
|
+
* (if any). */
|
792
|
+
protocol_s *http1_new(uintptr_t uuid, http_settings_s *settings,
|
793
|
+
void *unread_data, size_t unread_length) {
|
794
|
+
if (unread_data && unread_length > HTTP_MAX_HEADER_LENGTH)
|
478
795
|
return NULL;
|
796
|
+
http1pr_s *p = malloc(sizeof(*p) + HTTP_MAX_HEADER_LENGTH);
|
797
|
+
HTTP_ASSERT(p, "HTTP/1.1 protocol allocation failed");
|
798
|
+
*p = (http1pr_s){
|
799
|
+
.p.protocol =
|
800
|
+
{
|
801
|
+
.service = HTTP1_SERVICE_STR,
|
802
|
+
.on_data = http1_on_data_first_time,
|
803
|
+
.on_close = http1_on_close,
|
804
|
+
},
|
805
|
+
.p.uuid = uuid,
|
806
|
+
.p.settings = settings,
|
807
|
+
.max_header_size = settings->max_header_size,
|
808
|
+
.is_client = settings->is_client,
|
809
|
+
};
|
810
|
+
http_s_new(&p->request, &p->p, &HTTP1_VTABLE);
|
811
|
+
facil_attach(uuid, &p->p.protocol);
|
812
|
+
if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) {
|
813
|
+
memcpy(p->buf, unread_data, unread_length);
|
814
|
+
p->buf_len = unread_length;
|
815
|
+
facil_force_event(uuid, FIO_EVENT_ON_DATA);
|
479
816
|
}
|
817
|
+
return &p->p.protocol;
|
818
|
+
}
|
480
819
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
820
|
+
/** Manually destroys the HTTP1 protocol object. */
|
821
|
+
void http1_destroy(protocol_s *pr) {
|
822
|
+
http1pr_s *p = (http1pr_s *)pr;
|
823
|
+
http1_pr2handle(p).status = 0;
|
824
|
+
http_s_destroy(&http1_pr2handle(p), 0);
|
825
|
+
free(p);
|
826
|
+
}
|
827
|
+
|
828
|
+
/* *****************************************************************************
|
829
|
+
Protocol Data
|
830
|
+
***************************************************************************** */
|
831
|
+
|
832
|
+
// clang-format off
|
833
|
+
#define HTTP_SET_STATUS_STR(status, str) [((status)-100)] = { .buffer = ("HTTP/1.1 " #status " " str "\r\n"), .length = (sizeof("HTTP/1.1 " #status " " str "\r\n") - 1) }
|
834
|
+
// #undef HTTP_SET_STATUS_STR
|
835
|
+
// clang-format on
|
836
|
+
|
837
|
+
static fio_cstr_s http1pr_status2str(uintptr_t status) {
|
838
|
+
static fio_cstr_s status2str[] = {
|
839
|
+
HTTP_SET_STATUS_STR(100, "Continue"),
|
840
|
+
HTTP_SET_STATUS_STR(101, "Switching Protocols"),
|
841
|
+
HTTP_SET_STATUS_STR(102, "Processing"),
|
842
|
+
HTTP_SET_STATUS_STR(103, "Early Hints"),
|
843
|
+
HTTP_SET_STATUS_STR(200, "OK"),
|
844
|
+
HTTP_SET_STATUS_STR(201, "Created"),
|
845
|
+
HTTP_SET_STATUS_STR(202, "Accepted"),
|
846
|
+
HTTP_SET_STATUS_STR(203, "Non-Authoritative Information"),
|
847
|
+
HTTP_SET_STATUS_STR(204, "No Content"),
|
848
|
+
HTTP_SET_STATUS_STR(205, "Reset Content"),
|
849
|
+
HTTP_SET_STATUS_STR(206, "Partial Content"),
|
850
|
+
HTTP_SET_STATUS_STR(207, "Multi-Status"),
|
851
|
+
HTTP_SET_STATUS_STR(208, "Already Reported"),
|
852
|
+
HTTP_SET_STATUS_STR(226, "IM Used"),
|
853
|
+
HTTP_SET_STATUS_STR(300, "Multiple Choices"),
|
854
|
+
HTTP_SET_STATUS_STR(301, "Moved Permanently"),
|
855
|
+
HTTP_SET_STATUS_STR(302, "Found"),
|
856
|
+
HTTP_SET_STATUS_STR(303, "See Other"),
|
857
|
+
HTTP_SET_STATUS_STR(304, "Not Modified"),
|
858
|
+
HTTP_SET_STATUS_STR(305, "Use Proxy"),
|
859
|
+
HTTP_SET_STATUS_STR(306, "(Unused), "),
|
860
|
+
HTTP_SET_STATUS_STR(307, "Temporary Redirect"),
|
861
|
+
HTTP_SET_STATUS_STR(308, "Permanent Redirect"),
|
862
|
+
HTTP_SET_STATUS_STR(400, "Bad Request"),
|
863
|
+
HTTP_SET_STATUS_STR(403, "Forbidden"),
|
864
|
+
HTTP_SET_STATUS_STR(404, "Not Found"),
|
865
|
+
HTTP_SET_STATUS_STR(401, "Unauthorized"),
|
866
|
+
HTTP_SET_STATUS_STR(402, "Payment Required"),
|
867
|
+
HTTP_SET_STATUS_STR(405, "Method Not Allowed"),
|
868
|
+
HTTP_SET_STATUS_STR(406, "Not Acceptable"),
|
869
|
+
HTTP_SET_STATUS_STR(407, "Proxy Authentication Required"),
|
870
|
+
HTTP_SET_STATUS_STR(408, "Request Timeout"),
|
871
|
+
HTTP_SET_STATUS_STR(409, "Conflict"),
|
872
|
+
HTTP_SET_STATUS_STR(410, "Gone"),
|
873
|
+
HTTP_SET_STATUS_STR(411, "Length Required"),
|
874
|
+
HTTP_SET_STATUS_STR(412, "Precondition Failed"),
|
875
|
+
HTTP_SET_STATUS_STR(413, "Payload Too Large"),
|
876
|
+
HTTP_SET_STATUS_STR(414, "URI Too Long"),
|
877
|
+
HTTP_SET_STATUS_STR(415, "Unsupported Media Type"),
|
878
|
+
HTTP_SET_STATUS_STR(416, "Range Not Satisfiable"),
|
879
|
+
HTTP_SET_STATUS_STR(417, "Expectation Failed"),
|
880
|
+
HTTP_SET_STATUS_STR(421, "Misdirected Request"),
|
881
|
+
HTTP_SET_STATUS_STR(422, "Unprocessable Entity"),
|
882
|
+
HTTP_SET_STATUS_STR(423, "Locked"),
|
883
|
+
HTTP_SET_STATUS_STR(424, "Failed Dependency"),
|
884
|
+
HTTP_SET_STATUS_STR(425, "Unassigned"),
|
885
|
+
HTTP_SET_STATUS_STR(426, "Upgrade Required"),
|
886
|
+
HTTP_SET_STATUS_STR(427, "Unassigned"),
|
887
|
+
HTTP_SET_STATUS_STR(428, "Precondition Required"),
|
888
|
+
HTTP_SET_STATUS_STR(429, "Too Many Requests"),
|
889
|
+
HTTP_SET_STATUS_STR(430, "Unassigned"),
|
890
|
+
HTTP_SET_STATUS_STR(431, "Request Header Fields Too Large"),
|
891
|
+
HTTP_SET_STATUS_STR(500, "Internal Server Error"),
|
892
|
+
HTTP_SET_STATUS_STR(501, "Not Implemented"),
|
893
|
+
HTTP_SET_STATUS_STR(502, "Bad Gateway"),
|
894
|
+
HTTP_SET_STATUS_STR(503, "Service Unavailable"),
|
895
|
+
HTTP_SET_STATUS_STR(504, "Gateway Timeout"),
|
896
|
+
HTTP_SET_STATUS_STR(505, "HTTP Version Not Supported"),
|
897
|
+
HTTP_SET_STATUS_STR(506, "Variant Also Negotiates"),
|
898
|
+
HTTP_SET_STATUS_STR(507, "Insufficient Storage"),
|
899
|
+
HTTP_SET_STATUS_STR(508, "Loop Detected"),
|
900
|
+
HTTP_SET_STATUS_STR(509, "Unassigned"),
|
901
|
+
HTTP_SET_STATUS_STR(510, "Not Extended"),
|
902
|
+
HTTP_SET_STATUS_STR(511, "Network Authentication Required"),
|
903
|
+
};
|
904
|
+
fio_cstr_s ret = (fio_cstr_s){.length = 0, .buffer = NULL};
|
905
|
+
if (status >= 100 &&
|
906
|
+
(status - 100) < sizeof(status2str) / sizeof(status2str[0]))
|
907
|
+
ret = status2str[status - 100];
|
908
|
+
if (!ret.buffer) {
|
909
|
+
ret = status2str[400];
|
910
|
+
}
|
911
|
+
return ret;
|
488
912
|
}
|
913
|
+
#undef HTTP_SET_STATUS_STR
|