iodine 0.7.36 → 0.7.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.travis.yml +10 -8
- data/CHANGELOG.md +28 -0
- data/README.md +55 -48
- data/Rakefile +3 -1
- data/examples/shootout.ru +8 -0
- data/exe/iodine +1 -0
- data/ext/iodine/extconf.rb +0 -34
- data/ext/iodine/fio.c +65 -65
- data/ext/iodine/fio.h +1 -1
- data/ext/iodine/fio_tls_missing.c +6 -0
- data/ext/iodine/fio_tls_openssl.c +64 -28
- data/ext/iodine/http.c +87 -100
- data/ext/iodine/http1.c +6 -15
- data/ext/iodine/http1_parser.h +806 -63
- data/ext/iodine/iodine.c +12 -0
- data/ext/iodine/iodine_defer.c +1 -0
- data/ext/iodine/iodine_http.c +11 -0
- data/ext/iodine/iodine_http.h +2 -0
- data/ext/iodine/iodine_rack_io.c +1 -5
- data/ext/iodine/iodine_tls.c +2 -16
- data/iodine.gemspec +2 -2
- data/lib/iodine.rb +2 -1
- data/lib/iodine/connection.rb +12 -0
- data/lib/iodine/version.rb +1 -1
- metadata +7 -7
- data/ext/iodine/http1_parser.c +0 -458
data/ext/iodine/iodine.c
CHANGED
@@ -117,6 +117,8 @@ Core API
|
|
117
117
|
* i.e., -2 == half the number of detected CPU cores.
|
118
118
|
*
|
119
119
|
* Zero values promise nothing (iodine will decide what to do with them).
|
120
|
+
*
|
121
|
+
* @return [FixNum] Thread Count
|
120
122
|
*/
|
121
123
|
static VALUE iodine_threads_get(VALUE self) {
|
122
124
|
VALUE i = rb_ivar_get(self, rb_intern2("@threads", 8));
|
@@ -133,6 +135,8 @@ static VALUE iodine_threads_get(VALUE self) {
|
|
133
135
|
* i.e., -2 == half the number of detected CPU cores.
|
134
136
|
*
|
135
137
|
* Zero values promise nothing (iodine will decide what to do with them).
|
138
|
+
*
|
139
|
+
* @param thread_count [FixNum] The number of worker threads to use
|
136
140
|
*/
|
137
141
|
static VALUE iodine_threads_set(VALUE self, VALUE val) {
|
138
142
|
Check_Type(val, T_FIXNUM);
|
@@ -157,6 +161,8 @@ static VALUE iodine_threads_set(VALUE self, VALUE val) {
|
|
157
161
|
*
|
158
162
|
* Logging is always performed to the process's STDERR and can be piped away.
|
159
163
|
*
|
164
|
+
* @return [FixNum] Logging Level
|
165
|
+
*
|
160
166
|
* NOTE: this does NOT effect HTTP logging.
|
161
167
|
*/
|
162
168
|
static VALUE iodine_logging_get(VALUE self) {
|
@@ -178,6 +184,8 @@ static VALUE iodine_logging_get(VALUE self) {
|
|
178
184
|
*
|
179
185
|
* Logging is always performed to the process's STDERR and can be piped away.
|
180
186
|
*
|
187
|
+
* @param log_level [FixNum] Sets the logging level
|
188
|
+
*
|
181
189
|
* NOTE: this does NOT effect HTTP logging.
|
182
190
|
*/
|
183
191
|
static VALUE iodine_logging_set(VALUE self, VALUE val) {
|
@@ -196,6 +204,8 @@ static VALUE iodine_logging_set(VALUE self, VALUE val) {
|
|
196
204
|
* Zero values promise nothing (iodine will decide what to do with them).
|
197
205
|
*
|
198
206
|
* 1 == single process mode, the msater process acts as a worker process.
|
207
|
+
*
|
208
|
+
* @return [FixNum] Worker Count
|
199
209
|
*/
|
200
210
|
static VALUE iodine_workers_get(VALUE self) {
|
201
211
|
VALUE i = rb_ivar_get(self, rb_intern2("@workers", 8));
|
@@ -214,6 +224,8 @@ static VALUE iodine_workers_get(VALUE self) {
|
|
214
224
|
* Zero values promise nothing (iodine will decide what to do with them).
|
215
225
|
*
|
216
226
|
* 1 == single process mode, the msater process acts as a worker process.
|
227
|
+
*
|
228
|
+
* @param worker_count [FixNum] Number of worker processes
|
217
229
|
*/
|
218
230
|
static VALUE iodine_workers_set(VALUE self, VALUE val) {
|
219
231
|
Check_Type(val, T_FIXNUM);
|
data/ext/iodine/iodine_defer.c
CHANGED
data/ext/iodine/iodine_http.c
CHANGED
@@ -28,6 +28,8 @@ typedef struct {
|
|
28
28
|
} iodine_http_settings_s;
|
29
29
|
|
30
30
|
/* these three are used also by iodin_rack_io.c */
|
31
|
+
VALUE IODINE_R_INPUT_DEFAULT;
|
32
|
+
VALUE IODINE_R_INPUT;
|
31
33
|
VALUE IODINE_R_HIJACK;
|
32
34
|
VALUE IODINE_R_HIJACK_IO;
|
33
35
|
VALUE IODINE_R_HIJACK_CB;
|
@@ -846,6 +848,7 @@ static void initialize_env_template(void) {
|
|
846
848
|
}
|
847
849
|
add_str_to_env(env_template_no_upgrade, "SCRIPT_NAME", sn);
|
848
850
|
}
|
851
|
+
rb_hash_aset(env_template_no_upgrade, IODINE_R_INPUT, IODINE_R_INPUT_DEFAULT);
|
849
852
|
add_value_to_env(env_template_no_upgrade, "rack.errors", rb_stderr);
|
850
853
|
add_value_to_env(env_template_no_upgrade, "rack.hijack?", Qtrue);
|
851
854
|
add_value_to_env(env_template_no_upgrade, "rack.multiprocess", Qtrue);
|
@@ -1111,6 +1114,7 @@ void iodine_init_http(void) {
|
|
1111
1114
|
rack_set(XSENDFILE_TYPE_HEADER, "HTTP_X_SENDFILE_TYPE");
|
1112
1115
|
rack_set(CONTENT_LENGTH_HEADER, "Content-Length");
|
1113
1116
|
|
1117
|
+
rack_set(IODINE_R_INPUT, "rack.input");
|
1114
1118
|
rack_set(IODINE_R_HIJACK_IO, "rack.hijack_io");
|
1115
1119
|
rack_set(IODINE_R_HIJACK, "rack.hijack");
|
1116
1120
|
rack_set(IODINE_R_HIJACK_CB, "iodine.hijack_cb");
|
@@ -1132,5 +1136,12 @@ void iodine_init_http(void) {
|
|
1132
1136
|
IodineUTF8Encoding = rb_enc_find("UTF-8");
|
1133
1137
|
IodineBinaryEncoding = rb_enc_find("binary");
|
1134
1138
|
|
1139
|
+
{
|
1140
|
+
VALUE STRIO_CLASS = rb_const_get(rb_cObject, rb_intern("StringIO"));
|
1141
|
+
IODINE_R_INPUT_DEFAULT = rb_str_new_static("", 0);
|
1142
|
+
IODINE_R_INPUT_DEFAULT =
|
1143
|
+
rb_funcallv(STRIO_CLASS, rb_intern("new"), 1, &IODINE_R_INPUT_DEFAULT);
|
1144
|
+
rb_global_variable(&IODINE_R_INPUT_DEFAULT);
|
1145
|
+
}
|
1135
1146
|
initialize_env_template();
|
1136
1147
|
}
|
data/ext/iodine/iodine_http.h
CHANGED
@@ -9,6 +9,8 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
9
9
|
#include "iodine.h"
|
10
10
|
|
11
11
|
/* these three are used also by rb-rack-io.c */
|
12
|
+
extern VALUE IODINE_R_INPUT;
|
13
|
+
extern VALUE IODINE_R_INPUT_DEFAULT;
|
12
14
|
extern VALUE IODINE_R_HIJACK;
|
13
15
|
extern VALUE IODINE_R_HIJACK_IO;
|
14
16
|
extern VALUE IODINE_R_HIJACK_CB;
|
data/ext/iodine/iodine_rack_io.c
CHANGED
@@ -216,7 +216,7 @@ static VALUE new_rack_io(http_s *h, VALUE env) {
|
|
216
216
|
rb_ivar_set(rack_io, io_id, ULL2NUM(h->body));
|
217
217
|
set_handle(rack_io, h);
|
218
218
|
rb_ivar_set(rack_io, env_id, env);
|
219
|
-
rb_hash_aset(env,
|
219
|
+
rb_hash_aset(env, IODINE_R_INPUT, rack_io);
|
220
220
|
rb_hash_aset(env, IODINE_R_HIJACK, rb_obj_method(rack_io, hijack_func_sym));
|
221
221
|
return rack_io;
|
222
222
|
}
|
@@ -240,10 +240,6 @@ static void init_rack_io(void) {
|
|
240
240
|
iodine_new_func_id = rb_intern("new");
|
241
241
|
hijack_func_sym = ID2SYM(rb_intern("_hijack"));
|
242
242
|
|
243
|
-
R_INPUT = rb_enc_str_new("rack.input", 10, rb_ascii8bit_encoding());
|
244
|
-
rb_obj_freeze(R_INPUT);
|
245
|
-
IodineStore.add(R_INPUT);
|
246
|
-
|
247
243
|
TCPSOCKET_CLASS = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
248
244
|
// IO methods
|
249
245
|
|
data/ext/iodine/iodine_tls.c
CHANGED
@@ -208,22 +208,8 @@ static VALUE iodine_tls_alpn(VALUE self, VALUE protocol_name) {
|
|
208
208
|
}
|
209
209
|
|
210
210
|
/**
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
Iodine::Mustache.new(filename, template = nil)
|
215
|
-
|
216
|
-
When template data is provided, filename (if any) will only be used for
|
217
|
-
partial template path resolution and the template data will be used for the
|
218
|
-
template's content. This allows, for example, for front matter to be extracted
|
219
|
-
before parsing the template.
|
220
|
-
|
221
|
-
Once a template was loaded, it could be rendered using {render}.
|
222
|
-
|
223
|
-
Accepts named arguments as well:
|
224
|
-
|
225
|
-
Iodine::Mustache.new(filename: "foo.mustache", template: "{{ bar }}")
|
226
|
-
|
211
|
+
Creates a new {Iodine::TLS} object and calles the {#use_certificate} method with
|
212
|
+
the supplied arguments.
|
227
213
|
*/
|
228
214
|
static VALUE iodine_tls_new(int argc, VALUE *argv, VALUE self) {
|
229
215
|
if (argc) {
|
data/iodine.gemspec
CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
|
34
34
|
spec.requirements << 'A Unix based system: Linux / macOS / BSD.'
|
35
35
|
spec.requirements << 'An updated C compiler.'
|
36
|
-
spec.requirements << 'Ruby >= 2.
|
36
|
+
spec.requirements << 'Ruby >= 2.3.8 (Ruby EOL).'
|
37
37
|
spec.requirements << 'Ruby >= 2.5.0 recommended.'
|
38
38
|
spec.requirements << 'TLS requires OpenSSL >= 1.1.0'
|
39
39
|
|
@@ -45,5 +45,5 @@ Gem::Specification.new do |spec|
|
|
45
45
|
spec.add_development_dependency 'rake-compiler', '>= 1', '< 2.0'
|
46
46
|
|
47
47
|
spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n" +
|
48
|
-
"Remember: if iodine supports your business, it's
|
48
|
+
"Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations)."
|
49
49
|
end
|
data/lib/iodine.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'stringio' # Used internally as a default RackIO
|
1
2
|
require 'socket' # TCPSocket is used internally for Hijack support
|
2
3
|
# require 'openssl' # For SSL/TLS support using OpenSSL
|
3
4
|
|
@@ -177,7 +178,7 @@ Iodine.on_state(:after_fork) do
|
|
177
178
|
end
|
178
179
|
|
179
180
|
### Parse CLI for default HTTP settings
|
180
|
-
Iodine::Base::CLI.parse
|
181
|
+
Iodine::Base::CLI.parse if defined?(IODINE_PARSE_CLI) && IODINE_PARSE_CLI
|
181
182
|
|
182
183
|
### Set default port (if missing)
|
183
184
|
Iodine::DEFAULT_SETTINGS[:port] ||= (ENV["PORT"] || "3000")
|
data/lib/iodine/connection.rb
CHANGED
@@ -17,28 +17,40 @@ module Iodine
|
|
17
17
|
# Instances of this class are passed to the callback objects. i.e.:
|
18
18
|
#
|
19
19
|
# module MyConnectionCallbacks
|
20
|
+
#
|
20
21
|
# # called when the callback object is linked with a new client
|
21
22
|
# def on_open client
|
22
23
|
# client.is_a?(Iodine::Connection) # => true
|
23
24
|
# end
|
25
|
+
#
|
24
26
|
# # called when data is available
|
25
27
|
# def on_message client, data
|
26
28
|
# client.is_a?(Iodine::Connection) # => true
|
27
29
|
# end
|
30
|
+
#
|
28
31
|
# # called when the server is shutting down, before closing the client
|
29
32
|
# # (it's still possible to send messages to the client)
|
30
33
|
# def on_shutdown client
|
31
34
|
# client.is_a?(Iodine::Connection) # => true
|
32
35
|
# end
|
36
|
+
#
|
33
37
|
# # called when the client is closed (no longer available)
|
34
38
|
# def on_close client
|
35
39
|
# client.is_a?(Iodine::Connection) # => true
|
36
40
|
# end
|
41
|
+
#
|
37
42
|
# # called when all the previous calls to `client.write` have completed
|
38
43
|
# # (the local buffer was drained and is now empty)
|
39
44
|
# def on_drained client
|
40
45
|
# client.is_a?(Iodine::Connection) # => true
|
41
46
|
# end
|
47
|
+
#
|
48
|
+
# # called when timeout was reached, llowing a `ping` to be sent
|
49
|
+
# def ping client
|
50
|
+
# client.is_a?(Iodine::Connection) # => true
|
51
|
+
# clint.close() # close connection on timeout is the default
|
52
|
+
# end
|
53
|
+
#
|
42
54
|
# # Allows the module to be used as a static callback object (avoiding object allocation)
|
43
55
|
# extend self
|
44
56
|
# end
|
data/lib/iodine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iodine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.41
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -121,6 +121,7 @@ extensions:
|
|
121
121
|
- ext/iodine/extconf.rb
|
122
122
|
extra_rdoc_files: []
|
123
123
|
files:
|
124
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
124
125
|
- ".gitignore"
|
125
126
|
- ".rspec"
|
126
127
|
- ".travis.yml"
|
@@ -187,7 +188,6 @@ files:
|
|
187
188
|
- ext/iodine/http.h
|
188
189
|
- ext/iodine/http1.c
|
189
190
|
- ext/iodine/http1.h
|
190
|
-
- ext/iodine/http1_parser.c
|
191
191
|
- ext/iodine/http1_parser.h
|
192
192
|
- ext/iodine/http_internal.c
|
193
193
|
- ext/iodine/http_internal.h
|
@@ -243,8 +243,8 @@ licenses:
|
|
243
243
|
metadata:
|
244
244
|
allowed_push_host: https://rubygems.org
|
245
245
|
post_install_message: |-
|
246
|
-
Thank you for installing Iodine 0.7.
|
247
|
-
Remember: if iodine supports your business, it's
|
246
|
+
Thank you for installing Iodine 0.7.41.
|
247
|
+
Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations).
|
248
248
|
rdoc_options: []
|
249
249
|
require_paths:
|
250
250
|
- lib
|
@@ -262,10 +262,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
262
262
|
requirements:
|
263
263
|
- 'A Unix based system: Linux / macOS / BSD.'
|
264
264
|
- An updated C compiler.
|
265
|
-
- Ruby >= 2.
|
265
|
+
- Ruby >= 2.3.8 (Ruby EOL).
|
266
266
|
- Ruby >= 2.5.0 recommended.
|
267
267
|
- TLS requires OpenSSL >= 1.1.0
|
268
|
-
rubygems_version: 3.
|
268
|
+
rubygems_version: 3.1.2
|
269
269
|
signing_key:
|
270
270
|
specification_version: 4
|
271
271
|
summary: iodine - a fast HTTP / Websocket Server with Pub/Sub support, optimized for
|
data/ext/iodine/http1_parser.c
DELETED
@@ -1,458 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
Copyright: Boaz Segev, 2017-2019
|
3
|
-
License: MIT
|
4
|
-
|
5
|
-
Feel free to copy, use and enjoy according to the license provided.
|
6
|
-
*/
|
7
|
-
#ifndef __GNU_SOURCE
|
8
|
-
#define __GNU_SOURCE
|
9
|
-
#endif
|
10
|
-
|
11
|
-
#include <http1_parser.h>
|
12
|
-
|
13
|
-
#include <ctype.h>
|
14
|
-
#include <stdio.h>
|
15
|
-
#include <string.h>
|
16
|
-
|
17
|
-
/* *****************************************************************************
|
18
|
-
Seeking for characters in a string
|
19
|
-
***************************************************************************** */
|
20
|
-
|
21
|
-
#ifndef ALLOW_UNALIGNED_MEMORY_ACCESS
|
22
|
-
#define ALLOW_UNALIGNED_MEMORY_ACCESS 0
|
23
|
-
#endif
|
24
|
-
|
25
|
-
#if FIO_MEMCHAR
|
26
|
-
|
27
|
-
/**
|
28
|
-
* This seems to be faster on some systems, especially for smaller distances.
|
29
|
-
*
|
30
|
-
* On newer systems, `memchr` should be faster.
|
31
|
-
*/
|
32
|
-
static int seek2ch(uint8_t **buffer, register uint8_t *const limit,
|
33
|
-
const uint8_t c) {
|
34
|
-
if (**buffer == c) {
|
35
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
36
|
-
**buffer = 0;
|
37
|
-
#endif
|
38
|
-
return 1;
|
39
|
-
}
|
40
|
-
|
41
|
-
#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__)
|
42
|
-
/* too short for this mess */
|
43
|
-
if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7)))
|
44
|
-
goto finish;
|
45
|
-
|
46
|
-
/* align memory */
|
47
|
-
{
|
48
|
-
const uint8_t *alignment =
|
49
|
-
(uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8);
|
50
|
-
if (limit >= alignment) {
|
51
|
-
while (*buffer < alignment) {
|
52
|
-
if (**buffer == c) {
|
53
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
54
|
-
**buffer = 0;
|
55
|
-
#endif
|
56
|
-
return 1;
|
57
|
-
}
|
58
|
-
*buffer += 1;
|
59
|
-
}
|
60
|
-
}
|
61
|
-
}
|
62
|
-
const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7));
|
63
|
-
#else
|
64
|
-
const uint8_t *limit64 = (uint8_t *)limit - 7;
|
65
|
-
#endif
|
66
|
-
uint64_t wanted1 = 0x0101010101010101ULL * c;
|
67
|
-
for (; *buffer < limit64; *buffer += 8) {
|
68
|
-
const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1);
|
69
|
-
const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu;
|
70
|
-
const uint64_t t1 = (eq1 & 0x8080808080808080llu);
|
71
|
-
if ((t0 & t1)) {
|
72
|
-
break;
|
73
|
-
}
|
74
|
-
}
|
75
|
-
#if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__)
|
76
|
-
finish:
|
77
|
-
#endif
|
78
|
-
while (*buffer < limit) {
|
79
|
-
if (**buffer == c) {
|
80
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
81
|
-
**buffer = 0;
|
82
|
-
#endif
|
83
|
-
return 1;
|
84
|
-
}
|
85
|
-
(*buffer)++;
|
86
|
-
}
|
87
|
-
return 0;
|
88
|
-
}
|
89
|
-
|
90
|
-
#else
|
91
|
-
|
92
|
-
/* a helper that seeks any char, converts it to NUL and returns 1 if found. */
|
93
|
-
inline static uint8_t seek2ch(uint8_t **pos, uint8_t *const limit, uint8_t ch) {
|
94
|
-
/* This is library based alternative that is sometimes slower */
|
95
|
-
if (*pos >= limit || **pos == ch) {
|
96
|
-
return 0;
|
97
|
-
}
|
98
|
-
uint8_t *tmp = memchr(*pos, ch, limit - (*pos));
|
99
|
-
if (tmp) {
|
100
|
-
*pos = tmp;
|
101
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
102
|
-
*tmp = 0;
|
103
|
-
#endif
|
104
|
-
return 1;
|
105
|
-
}
|
106
|
-
*pos = limit;
|
107
|
-
return 0;
|
108
|
-
}
|
109
|
-
|
110
|
-
#endif
|
111
|
-
|
112
|
-
/* a helper that seeks the EOL, converts it to NUL and returns it's length */
|
113
|
-
inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) {
|
114
|
-
/* single char lookup using memchr might be better when target is far... */
|
115
|
-
if (!seek2ch(pos, limit, '\n'))
|
116
|
-
return 0;
|
117
|
-
if ((*pos)[-1] == '\r') {
|
118
|
-
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
119
|
-
(*pos)[-1] = 0;
|
120
|
-
#endif
|
121
|
-
return 2;
|
122
|
-
}
|
123
|
-
return 1;
|
124
|
-
}
|
125
|
-
|
126
|
-
/* *****************************************************************************
|
127
|
-
HTTP/1.1 parsre stages
|
128
|
-
***************************************************************************** */
|
129
|
-
|
130
|
-
inline static int consume_response_line(struct http1_fio_parser_args_s *args,
|
131
|
-
uint8_t *start, uint8_t *end) {
|
132
|
-
args->parser->state.reserved |= 128;
|
133
|
-
uint8_t *tmp = start;
|
134
|
-
if (!seek2ch(&tmp, end, ' '))
|
135
|
-
return -1;
|
136
|
-
if (args->on_http_version(args->parser, (char *)start, tmp - start))
|
137
|
-
return -1;
|
138
|
-
tmp = start = tmp + 1;
|
139
|
-
if (!seek2ch(&tmp, end, ' '))
|
140
|
-
return -1;
|
141
|
-
if (args->on_status(args->parser, atol((char *)start), (char *)(tmp + 1),
|
142
|
-
end - tmp))
|
143
|
-
return -1;
|
144
|
-
return 0;
|
145
|
-
}
|
146
|
-
|
147
|
-
inline static int consume_request_line(struct http1_fio_parser_args_s *args,
|
148
|
-
uint8_t *start, uint8_t *end) {
|
149
|
-
uint8_t *tmp = start;
|
150
|
-
uint8_t *host_start = NULL;
|
151
|
-
uint8_t *host_end = NULL;
|
152
|
-
if (!seek2ch(&tmp, end, ' '))
|
153
|
-
return -1;
|
154
|
-
if (args->on_method(args->parser, (char *)start, tmp - start))
|
155
|
-
return -1;
|
156
|
-
tmp = start = tmp + 1;
|
157
|
-
if (start[0] == 'h' && start[1] == 't' && start[2] == 't' &&
|
158
|
-
start[3] == 'p') {
|
159
|
-
if (start[4] == ':' && start[5] == '/' && start[6] == '/') {
|
160
|
-
/* Request URI is in long form... emulate Host header instead. */
|
161
|
-
tmp = host_end = host_start = (start += 7);
|
162
|
-
} else if (start[4] == 's' && start[5] == ':' && start[6] == '/' &&
|
163
|
-
start[7] == '/') {
|
164
|
-
/* Secure request is in long form... emulate Host header instead. */
|
165
|
-
tmp = host_end = host_start = (start += 8);
|
166
|
-
} else
|
167
|
-
goto review_path;
|
168
|
-
if (!seek2ch(&tmp, end, ' '))
|
169
|
-
return -1;
|
170
|
-
*tmp = ' ';
|
171
|
-
if (!seek2ch(&host_end, tmp, '/')) {
|
172
|
-
if (args->on_path(args->parser, (char *)"/", 1))
|
173
|
-
return -1;
|
174
|
-
goto start_version;
|
175
|
-
}
|
176
|
-
host_end[0] = '/';
|
177
|
-
start = host_end;
|
178
|
-
}
|
179
|
-
review_path:
|
180
|
-
tmp = start;
|
181
|
-
if (seek2ch(&tmp, end, '?')) {
|
182
|
-
if (args->on_path(args->parser, (char *)start, tmp - start))
|
183
|
-
return -1;
|
184
|
-
tmp = start = tmp + 1;
|
185
|
-
if (!seek2ch(&tmp, end, ' '))
|
186
|
-
return -1;
|
187
|
-
if (tmp - start > 0 &&
|
188
|
-
args->on_query(args->parser, (char *)start, tmp - start))
|
189
|
-
return -1;
|
190
|
-
} else {
|
191
|
-
tmp = start;
|
192
|
-
if (!seek2ch(&tmp, end, ' '))
|
193
|
-
return -1;
|
194
|
-
if (args->on_path(args->parser, (char *)start, tmp - start))
|
195
|
-
return -1;
|
196
|
-
}
|
197
|
-
start_version:
|
198
|
-
start = tmp + 1;
|
199
|
-
if (start + 5 >= end) /* require "HTTP/" */
|
200
|
-
return -1;
|
201
|
-
if (args->on_http_version(args->parser, (char *)start, end - start))
|
202
|
-
return -1;
|
203
|
-
/* */
|
204
|
-
if (host_start && args->on_header(args->parser, (char *)"host", 4,
|
205
|
-
(char *)host_start, host_end - host_start))
|
206
|
-
return -1;
|
207
|
-
return 0;
|
208
|
-
}
|
209
|
-
|
210
|
-
inline static int consume_header(struct http1_fio_parser_args_s *args,
|
211
|
-
uint8_t *start, uint8_t *end) {
|
212
|
-
uint8_t *end_name = start;
|
213
|
-
/* divide header name from data */
|
214
|
-
if (!seek2ch(&end_name, end, ':'))
|
215
|
-
return -1;
|
216
|
-
#if HTTP_HEADERS_LOWERCASE
|
217
|
-
for (uint8_t *t = start; t < end_name; t++) {
|
218
|
-
*t = tolower(*t);
|
219
|
-
}
|
220
|
-
#endif
|
221
|
-
uint8_t *start_value = end_name + 1;
|
222
|
-
if (start_value[0] == ' ') {
|
223
|
-
start_value++;
|
224
|
-
};
|
225
|
-
#if ALLOW_UNALIGNED_MEMORY_ACCESS && HTTP_HEADERS_LOWERCASE
|
226
|
-
/* enable this section to test unaligned memory access */
|
227
|
-
if ((end_name - start) == 14 &&
|
228
|
-
*((uint64_t *)start) == *((uint64_t *)"content-") &&
|
229
|
-
*((uint64_t *)(start + 6)) == *((uint64_t *)"t-length")) {
|
230
|
-
/* handle the special `content-length` header */
|
231
|
-
args->parser->state.content_length = atol((char *)start_value);
|
232
|
-
} else if ((end_name - start) == 17 &&
|
233
|
-
*((uint64_t *)start) == *((uint64_t *)"transfer") &&
|
234
|
-
*((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin") &&
|
235
|
-
*((uint32_t *)start_value) == *((uint32_t *)"chun") &&
|
236
|
-
*((uint32_t *)(start_value + 3)) == *((uint32_t *)"nked")) {
|
237
|
-
/* handle the special `transfer-encoding: chunked` header */
|
238
|
-
args->parser->state.reserved |= 64;
|
239
|
-
} else if ((end_name - start) == 7 &&
|
240
|
-
*((uint64_t *)start) == *((uint64_t *)"trailer")) {
|
241
|
-
/* chunked data with trailer... */
|
242
|
-
args->parser->state.reserved |= 64;
|
243
|
-
args->parser->state.reserved |= 32;
|
244
|
-
}
|
245
|
-
#else
|
246
|
-
if ((end_name - start) == 14 &&
|
247
|
-
HEADER_NAME_IS_EQ((char *)start, "content-length", 14)) {
|
248
|
-
/* handle the special `content-length` header */
|
249
|
-
args->parser->state.content_length = atol((char *)start_value);
|
250
|
-
} else if ((end_name - start) == 17 &&
|
251
|
-
HEADER_NAME_IS_EQ((char *)start, "transfer-encoding", 17) &&
|
252
|
-
memcmp(start_value, "chunked", 7)) {
|
253
|
-
/* handle the special `transfer-encoding: chunked` header */
|
254
|
-
args->parser->state.reserved |= 64;
|
255
|
-
} else if ((end_name - start) == 7 &&
|
256
|
-
HEADER_NAME_IS_EQ((char *)start, "trailer", 7)) {
|
257
|
-
/* chunked data with trailer... */
|
258
|
-
args->parser->state.reserved |= 64;
|
259
|
-
args->parser->state.reserved |= 32;
|
260
|
-
}
|
261
|
-
#endif
|
262
|
-
/* perform callback */
|
263
|
-
if (args->on_header(args->parser, (char *)start, (end_name - start),
|
264
|
-
(char *)start_value, end - start_value))
|
265
|
-
return -1;
|
266
|
-
return 0;
|
267
|
-
}
|
268
|
-
|
269
|
-
/* *****************************************************************************
|
270
|
-
HTTP/1.1 Body handling
|
271
|
-
***************************************************************************** */
|
272
|
-
|
273
|
-
inline static int consume_body_streamed(struct http1_fio_parser_args_s *args,
|
274
|
-
uint8_t **start) {
|
275
|
-
uint8_t *end =
|
276
|
-
*start + args->parser->state.content_length - args->parser->state.read;
|
277
|
-
uint8_t *const stop = ((uint8_t *)args->buffer) + args->length;
|
278
|
-
if (end > stop)
|
279
|
-
end = stop;
|
280
|
-
if (end > *start &&
|
281
|
-
args->on_body_chunk(args->parser, (char *)(*start), end - *start))
|
282
|
-
return -1;
|
283
|
-
args->parser->state.read += (end - *start);
|
284
|
-
*start = end;
|
285
|
-
if (args->parser->state.content_length <= args->parser->state.read)
|
286
|
-
args->parser->state.reserved |= 4;
|
287
|
-
return 0;
|
288
|
-
}
|
289
|
-
|
290
|
-
inline static int consume_body_chunked(struct http1_fio_parser_args_s *args,
|
291
|
-
uint8_t **start) {
|
292
|
-
uint8_t *const stop = ((uint8_t *)args->buffer) + args->length;
|
293
|
-
uint8_t *end = *start;
|
294
|
-
while (*start < stop) {
|
295
|
-
if (args->parser->state.content_length == 0) {
|
296
|
-
size_t eol_len;
|
297
|
-
/* consume seperator */
|
298
|
-
while (*start < stop && (**start == '\n' || **start == '\r'))
|
299
|
-
++(*start);
|
300
|
-
/* collect chunked length */
|
301
|
-
if (!(eol_len = seek2eol(&end, stop))) {
|
302
|
-
/* requires length data to continue */
|
303
|
-
return 0;
|
304
|
-
}
|
305
|
-
/* an empty EOL is possible in mid stream processing */
|
306
|
-
if (*start + eol_len > end && (*start = end) && !seek2eol(&end, stop)) {
|
307
|
-
return 0;
|
308
|
-
}
|
309
|
-
args->parser->state.content_length = 0 - strtol((char *)*start, NULL, 16);
|
310
|
-
*start = end = end + 1;
|
311
|
-
if (args->parser->state.content_length == 0) {
|
312
|
-
/* all chunked data was parsed */
|
313
|
-
args->parser->state.content_length = args->parser->state.read;
|
314
|
-
/* consume trailing EOL */
|
315
|
-
if (seek2eol(start, stop))
|
316
|
-
(*start)++;
|
317
|
-
if (args->parser->state.reserved & 32) {
|
318
|
-
/* remove the "headers complete" and "trailer" flags */
|
319
|
-
args->parser->state.reserved &= 0xDD; /* 0xDD == ~2 & ~32 & 0xFF */
|
320
|
-
return -2;
|
321
|
-
}
|
322
|
-
/* the parsing complete flag */
|
323
|
-
args->parser->state.reserved |= 4;
|
324
|
-
return 0;
|
325
|
-
}
|
326
|
-
}
|
327
|
-
end = *start + (0 - args->parser->state.content_length);
|
328
|
-
if (end > stop)
|
329
|
-
end = stop;
|
330
|
-
if (end > *start &&
|
331
|
-
args->on_body_chunk(args->parser, (char *)(*start), end - *start)) {
|
332
|
-
return -1;
|
333
|
-
}
|
334
|
-
args->parser->state.read += (end - *start);
|
335
|
-
args->parser->state.content_length += (end - *start);
|
336
|
-
*start = end;
|
337
|
-
}
|
338
|
-
return 0;
|
339
|
-
}
|
340
|
-
|
341
|
-
inline static int consume_body(struct http1_fio_parser_args_s *args,
|
342
|
-
uint8_t **start) {
|
343
|
-
if (args->parser->state.content_length > 0 &&
|
344
|
-
args->parser->state.content_length > args->parser->state.read) {
|
345
|
-
/* normal, streamed data */
|
346
|
-
return consume_body_streamed(args, start);
|
347
|
-
} else if (args->parser->state.content_length <= 0 &&
|
348
|
-
(args->parser->state.reserved & 64)) {
|
349
|
-
/* chuncked encoding */
|
350
|
-
return consume_body_chunked(args, start);
|
351
|
-
} else {
|
352
|
-
/* nothing to do - parsing complete */
|
353
|
-
args->parser->state.reserved |= 4;
|
354
|
-
}
|
355
|
-
return 0;
|
356
|
-
}
|
357
|
-
|
358
|
-
/* *****************************************************************************
|
359
|
-
HTTP/1.1 parsre function
|
360
|
-
***************************************************************************** */
|
361
|
-
#if DEBUG
|
362
|
-
#include <assert.h>
|
363
|
-
#else
|
364
|
-
#define DEBUG 0
|
365
|
-
#define assert(...)
|
366
|
-
#endif
|
367
|
-
|
368
|
-
size_t http1_fio_parser_fn(struct http1_fio_parser_args_s *args) {
|
369
|
-
assert(args->parser && args->buffer);
|
370
|
-
args->parser->state.next = NULL;
|
371
|
-
uint8_t *start = args->buffer;
|
372
|
-
uint8_t *end = start;
|
373
|
-
uint8_t *const stop = start + args->length;
|
374
|
-
uint8_t eol_len = 0;
|
375
|
-
#define CONSUMED ((size_t)((uintptr_t)start - (uintptr_t)args->buffer))
|
376
|
-
// fprintf(stderr, "** resuming with at %p with %.*s...(%lu)\n", args->buffer,
|
377
|
-
// 4,
|
378
|
-
// start, args->length);
|
379
|
-
re_eval:
|
380
|
-
switch ((args->parser->state.reserved & 15)) {
|
381
|
-
|
382
|
-
/* request / response line */
|
383
|
-
case 0:
|
384
|
-
/* clear out any leadinng white space */
|
385
|
-
while ((start < stop) &&
|
386
|
-
(*start == '\r' || *start == '\n' || *start == ' ' || *start == 0)) {
|
387
|
-
++start;
|
388
|
-
}
|
389
|
-
end = start;
|
390
|
-
/* make sure the whole line is available*/
|
391
|
-
if (!(eol_len = seek2eol(&end, stop)))
|
392
|
-
return CONSUMED;
|
393
|
-
|
394
|
-
if (start[0] == 'H' && start[1] == 'T' && start[2] == 'T' &&
|
395
|
-
start[3] == 'P') {
|
396
|
-
/* HTTP response */
|
397
|
-
if (consume_response_line(args, start, end - eol_len + 1))
|
398
|
-
goto error;
|
399
|
-
} else if (tolower(start[0]) >= 'a' && tolower(start[0]) <= 'z') {
|
400
|
-
/* HTTP request */
|
401
|
-
if (consume_request_line(args, start, end - eol_len + 1))
|
402
|
-
goto error;
|
403
|
-
}
|
404
|
-
end = start = end + 1;
|
405
|
-
args->parser->state.reserved |= 1;
|
406
|
-
|
407
|
-
/* fallthrough */
|
408
|
-
/* headers */
|
409
|
-
case 1:
|
410
|
-
do {
|
411
|
-
if (start >= stop)
|
412
|
-
return CONSUMED; /* buffer ended on header line */
|
413
|
-
if (*start == '\r' || *start == '\n') {
|
414
|
-
goto finished_headers; /* empty line, end of headers */
|
415
|
-
}
|
416
|
-
if (!(eol_len = seek2eol(&end, stop)))
|
417
|
-
return CONSUMED;
|
418
|
-
if (consume_header(args, start, end - eol_len + 1))
|
419
|
-
goto error;
|
420
|
-
end = start = end + 1;
|
421
|
-
} while ((args->parser->state.reserved & 2) == 0);
|
422
|
-
finished_headers:
|
423
|
-
++start;
|
424
|
-
if (*start == '\n')
|
425
|
-
++start;
|
426
|
-
end = start;
|
427
|
-
args->parser->state.reserved |= 2;
|
428
|
-
/* fallthrough */
|
429
|
-
/* request body */
|
430
|
-
case 3: { /* 2 | 1 == 3 */
|
431
|
-
int t3 = consume_body(args, &start);
|
432
|
-
switch (t3) {
|
433
|
-
case -1:
|
434
|
-
goto error;
|
435
|
-
case -2:
|
436
|
-
goto re_eval;
|
437
|
-
}
|
438
|
-
break;
|
439
|
-
}
|
440
|
-
}
|
441
|
-
/* are we done ? */
|
442
|
-
if (args->parser->state.reserved & 4) {
|
443
|
-
args->parser->state.next = start;
|
444
|
-
if (((args->parser->state.reserved & 128) ? args->on_response
|
445
|
-
: args->on_request)(args->parser))
|
446
|
-
goto error;
|
447
|
-
args->parser->state =
|
448
|
-
(struct http1_parser_protected_read_only_state_s){0, 0, 0};
|
449
|
-
}
|
450
|
-
return CONSUMED;
|
451
|
-
error:
|
452
|
-
args->on_error(args->parser);
|
453
|
-
args->parser->state =
|
454
|
-
(struct http1_parser_protected_read_only_state_s){0, 0, 0};
|
455
|
-
return args->length;
|
456
|
-
}
|
457
|
-
|
458
|
-
#undef CONSUMED
|