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.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
data/ext/iodine/http1.c CHANGED
@@ -1,190 +1,594 @@
1
1
  /*
2
- copyright: Boaz segev, 2016-2017
3
- license: MIT
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 "http.h"
7
+ #include "http1.h"
10
8
  #include "http1_parser.h"
11
- #include "http1_request.h"
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
- #include <fcntl.h>
14
- #include <stdio.h>
15
- #include <string.h>
16
- #include <sys/stat.h>
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 data structures
27
+ The HTTP/1.1 Protocol Object
21
28
  ***************************************************************************** */
22
29
 
23
- typedef struct http1_protocol_s {
24
- protocol_s protocol;
25
- http_settings_s *settings;
30
+ typedef struct http1pr_s {
31
+ http_protocol_s p;
26
32
  http1_parser_s parser;
27
- void (*on_request)(http_request_s *request);
28
- struct http1_protocol_s *next;
29
- http1_request_s request;
30
- size_t len; /* used as a persistent socket `read` indication. */
31
- size_t refresh; /* a flag indicating a request callback was called. */
32
- } http1_protocol_s;
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
- static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol);
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/1.1 pool
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 struct {
41
- spn_lock_i lock;
42
- uint8_t init;
43
- http1_protocol_s *next;
44
- http1_protocol_s protocol_mem[HTTP1_POOL_SIZE];
45
- } http1_pool = {.lock = SPN_LOCK_INIT, .init = 0};
46
-
47
- static void http1_free(intptr_t uuid, http1_protocol_s *pr) {
48
- if ((uintptr_t)pr < (uintptr_t)http1_pool.protocol_mem ||
49
- (uintptr_t)pr >= (uintptr_t)(http1_pool.protocol_mem + HTTP1_POOL_SIZE))
50
- goto use_free;
51
- spn_lock(&http1_pool.lock);
52
- pr->next = http1_pool.next;
53
- http1_pool.next = pr;
54
- spn_unlock(&http1_pool.lock);
55
- return;
56
- use_free:
57
- free(pr);
58
- return;
59
- (void)uuid;
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
- static inline void http1_set_protocol_data(http1_protocol_s *pr) {
63
- pr->protocol = (protocol_s){
64
- .on_data = (void (*)(intptr_t, protocol_s *))http1_on_data,
65
- .on_close = (void (*)(intptr_t uuid, protocol_s *))http1_free};
66
- pr->request.request = (http_request_s){.fd = 0, .http_version = HTTP_V1};
67
- pr->refresh = pr->request.header_pos = pr->request.buffer_pos = pr->len = 0;
68
- pr->parser = (http1_parser_s){.udata = pr};
69
- }
70
-
71
- static http1_protocol_s *http1_alloc(void) {
72
- http1_protocol_s *pr;
73
- spn_lock(&http1_pool.lock);
74
- if (!http1_pool.next)
75
- goto use_malloc;
76
- pr = http1_pool.next;
77
- http1_pool.next = pr->next;
78
- spn_unlock(&http1_pool.lock);
79
- http1_request_clear(&pr->request.request);
80
- pr->request.request.settings = pr->settings;
81
- pr->len = 0;
82
- return pr;
83
- use_malloc:
84
- if (http1_pool.init == 0)
85
- goto initialize;
86
- spn_unlock(&http1_pool.lock);
87
- // fprintf(stderr, "using malloc\n");
88
- pr = malloc(sizeof(*pr));
89
- http1_set_protocol_data(pr);
90
- return pr;
91
- initialize:
92
- http1_pool.init = 1;
93
- for (size_t i = 1; i < (HTTP1_POOL_SIZE - 1); i++) {
94
- http1_set_protocol_data(http1_pool.protocol_mem + i);
95
- http1_pool.protocol_mem[i].next = http1_pool.protocol_mem + (i + 1);
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
- http1_pool.protocol_mem[HTTP1_POOL_SIZE - 1].next = NULL;
98
- http1_pool.next = http1_pool.protocol_mem + 1;
99
- spn_unlock(&http1_pool.lock);
100
- http1_set_protocol_data(http1_pool.protocol_mem);
101
- return http1_pool.protocol_mem;
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
- HTTP/1.1 error responses
296
+ Websockets Upgrading
106
297
  ***************************************************************************** */
107
298
 
108
- static void err_bad_request(http1_protocol_s *pr) {
109
- http_response_s *response = http_response_create(&pr->request.request);
110
- if (pr->settings->log_static)
111
- http_response_log_start(response);
112
- response->status = 400;
113
- if (!pr->settings->public_folder ||
114
- http_response_sendfile2(response, &pr->request.request,
115
- pr->settings->public_folder,
116
- pr->settings->public_folder_length, "400.html", 8,
117
- pr->settings->log_static)) {
118
- response->should_close = 1;
119
- http_response_write_body(response, "Bad Request", 11);
120
- http_response_finish(response);
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
- sock_close(pr->request.request.fd);
123
- pr->request.buffer_pos = 0;
124
- }
125
-
126
- static void err_too_big(http1_protocol_s *pr) {
127
- http_response_s *response = http_response_create(&pr->request.request);
128
- if (pr->settings->log_static)
129
- http_response_log_start(response);
130
- response->status = 431;
131
- if (!pr->settings->public_folder ||
132
- http_response_sendfile2(response, &pr->request.request,
133
- pr->settings->public_folder,
134
- pr->settings->public_folder_length, "431.html", 8,
135
- pr->settings->log_static)) {
136
- http_response_write_body(response, "Request Header Fields Too Large", 31);
137
- http_response_finish(response);
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
- HTTP/1.1 parsre callbacks
433
+ EventSource Support (SSE)
143
434
  ***************************************************************************** */
144
435
 
145
- #define HTTP_BODY_CHUNK_SIZE 4096
436
+ #undef http_upgrade2sse
146
437
 
147
- /** called when a request was received. */
148
- static int http1_on_request(http1_parser_s *parser) {
149
- http1_protocol_s *pr = parser->udata;
150
- if (!pr)
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
- if (pr->request.request.host == NULL) {
153
- goto bad_request;
154
- }
155
- pr->request.request.content_length = parser->state.content_length;
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
- bad_request:
171
- /* handle generally bad requests */
172
- err_bad_request(pr);
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
- return -1;
178
- (void)parser;
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
- http1_protocol_s *pr = parser->udata;
184
- if (!pr)
185
- return -1;
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
- return -1;
196
- (void)parser;
197
- (void)status;
198
- (void)status_str;
199
- (void)len;
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 path_len) {
204
- http1_protocol_s *pr = parser->udata;
205
- if (!pr)
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
- size_t query_len) {
215
- http1_protocol_s *pr = parser->udata;
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
- http1_protocol_s *pr = parser->udata;
227
- if (!pr)
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
- http1_protocol_s *pr = parser->udata;
238
- if (!pr || pr->request.header_pos >= HTTP1_MAX_HEADER_COUNT - 1)
239
- goto too_big;
240
- if (parser->state.read)
241
- goto too_big; /* refuse trailer header data, it isn't in buffer */
242
-
243
- /** test for special headers that should be easily accessible **/
244
- #if HTTP_HEADERS_LOWERCASE
245
- if (name_len == 4 && *((uint32_t *)name) == *((uint32_t *)"host")) {
246
- pr->request.request.host = data;
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
- #else
261
- if (name_len == 4 && HEADER_NAME_IS_EQ(name, "host", name_len)) {
262
- pr->request.request.host = data;
263
- pr->request.request.host_len = data_len;
264
- } else if (name_len == 12 &&
265
- HEADER_NAME_IS_EQ(name, "content-type", name_len)) {
266
- pr->request.request.content_type = data;
267
- pr->request.request.content_type_len = data_len;
268
- } else if (name_len == 7 && HEADER_NAME_IS_EQ(name, "upgrade", name_len)) {
269
- pr->request.request.upgrade = data;
270
- pr->request.request.upgrade_len = data_len;
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
- #endif
277
- pr->request.headers[pr->request.header_pos].name = name;
278
- pr->request.headers[pr->request.header_pos].name_len = name_len;
279
- pr->request.headers[pr->request.header_pos].data = data;
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
- http1_protocol_s *pr = parser->udata;
294
- if (!pr)
295
- return -1;
296
- if (parser->state.content_length > (ssize_t)pr->settings->max_body_size ||
297
- parser->state.read > (ssize_t)pr->settings->max_body_size)
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
- (pr->request.buffer_pos + parser->state.content_length <
302
- HTTP1_MAX_HEADER_SIZE)) {
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
- // create a temporary file to contain the data.
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
- if (pr->request.request.body_file) {
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
- http1_protocol_s *pr = parser->udata;
333
- if (!pr)
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
- HTTP/1.1 protocol callbacks
342
- ***************************************************************************** */
687
+ Connection Callbacks
688
+ *****************************************************************************
689
+ */
343
690
 
344
- /* parse and call callback */
345
- static void http1_on_data(intptr_t uuid, http1_protocol_s *pr) {
346
- size_t consumed;
347
- char buff[HTTP_BODY_CHUNK_SIZE];
348
- http1_request_s *request = &pr->request;
349
- char *buffer = request->buffer;
350
- ssize_t tmp = 0;
351
- // handle requests with no file data
352
- if (request->request.body_file <= 0) {
353
- // read into the request buffer.
354
- tmp = sock_read(uuid, request->buffer + request->buffer_pos,
355
- HTTP1_MAX_HEADER_SIZE - request->buffer_pos);
356
- if (tmp > 0) {
357
- request->buffer_pos += tmp;
358
- pr->len += tmp;
359
- } else
360
- tmp = 0;
361
- buffer = request->buffer + request->buffer_pos - pr->len;
362
- } else {
363
- if (pr->len) {
364
- /* protocol error, we shouldn't have letfovers during file processing */
365
- err_bad_request(pr);
366
- http1_on_error(&pr->parser);
367
- return;
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 (pr->len == 0)
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
- // parse HTTP data
382
- consumed =
383
- http1_fio_parser(.parser = &pr->parser, .buffer = buffer,
384
- .length = pr->len, .on_request = http1_on_request,
385
- .on_response = http1_on_response,
386
- .on_method = http1_on_method,
387
- .on_status = http1_on_status, .on_path = http1_on_path,
388
- .on_query = http1_on_query,
389
- .on_http_version = http1_on_http_version,
390
- .on_header = http1_on_header,
391
- .on_body_chunk = http1_on_body_chunk,
392
- .on_error = http1_on_error);
393
-
394
- // handle leftovers, if any
395
- if (pr->refresh) {
396
- pr->refresh = 0;
397
- if (pr->len > consumed) {
398
- memmove(request->buffer, buffer + consumed, pr->len - consumed);
399
- pr->len = pr->len - consumed;
400
- http1_request_clear(&request->request);
401
- request->buffer_pos = pr->len;
402
- facil_force_event(uuid, FIO_EVENT_ON_DATA);
403
- return;
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
- if (tmp)
411
- facil_force_event(uuid, FIO_EVENT_ON_DATA);
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
- HTTP listening helpers
786
+ Public API
787
+ *****************************************************************************
416
788
  */
417
789
 
418
- /**
419
- Allocates memory for an upgradable HTTP/1.1 protocol.
420
-
421
- The protocol self destructs when the `on_close` callback is called.
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
- busy_no_file:
482
- sock_write(fd,
483
- "HTTP/1.1 503 Service Unavailable\r\n"
484
- "Content-Length: 13\r\n\r\nServer Busy.",
485
- 68);
486
- sock_close(fd);
487
- return NULL;
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