pitchfork 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pitchfork might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.git-blame-ignore-revs +3 -0
- data/.gitattributes +5 -0
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +23 -0
- data/COPYING +674 -0
- data/Dockerfile +4 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +67 -0
- data/README.md +123 -0
- data/Rakefile +72 -0
- data/docs/Application_Timeouts.md +74 -0
- data/docs/CONFIGURATION.md +388 -0
- data/docs/DESIGN.md +86 -0
- data/docs/FORK_SAFETY.md +80 -0
- data/docs/PHILOSOPHY.md +90 -0
- data/docs/REFORKING.md +113 -0
- data/docs/SIGNALS.md +38 -0
- data/docs/TUNING.md +106 -0
- data/examples/constant_caches.ru +43 -0
- data/examples/echo.ru +25 -0
- data/examples/hello.ru +5 -0
- data/examples/nginx.conf +156 -0
- data/examples/pitchfork.conf.minimal.rb +5 -0
- data/examples/pitchfork.conf.rb +77 -0
- data/examples/unicorn.socket +11 -0
- data/exe/pitchfork +116 -0
- data/ext/pitchfork_http/CFLAGS +13 -0
- data/ext/pitchfork_http/c_util.h +116 -0
- data/ext/pitchfork_http/child_subreaper.h +25 -0
- data/ext/pitchfork_http/common_field_optimization.h +130 -0
- data/ext/pitchfork_http/epollexclusive.h +124 -0
- data/ext/pitchfork_http/ext_help.h +38 -0
- data/ext/pitchfork_http/extconf.rb +14 -0
- data/ext/pitchfork_http/global_variables.h +97 -0
- data/ext/pitchfork_http/httpdate.c +79 -0
- data/ext/pitchfork_http/pitchfork_http.c +4318 -0
- data/ext/pitchfork_http/pitchfork_http.rl +1024 -0
- data/ext/pitchfork_http/pitchfork_http_common.rl +76 -0
- data/lib/pitchfork/app/old_rails/static.rb +59 -0
- data/lib/pitchfork/children.rb +124 -0
- data/lib/pitchfork/configurator.rb +314 -0
- data/lib/pitchfork/const.rb +23 -0
- data/lib/pitchfork/http_parser.rb +206 -0
- data/lib/pitchfork/http_response.rb +63 -0
- data/lib/pitchfork/http_server.rb +822 -0
- data/lib/pitchfork/launcher.rb +9 -0
- data/lib/pitchfork/mem_info.rb +36 -0
- data/lib/pitchfork/message.rb +130 -0
- data/lib/pitchfork/mold_selector.rb +29 -0
- data/lib/pitchfork/preread_input.rb +33 -0
- data/lib/pitchfork/refork_condition.rb +21 -0
- data/lib/pitchfork/select_waiter.rb +9 -0
- data/lib/pitchfork/socket_helper.rb +199 -0
- data/lib/pitchfork/stream_input.rb +152 -0
- data/lib/pitchfork/tee_input.rb +133 -0
- data/lib/pitchfork/tmpio.rb +35 -0
- data/lib/pitchfork/version.rb +8 -0
- data/lib/pitchfork/worker.rb +244 -0
- data/lib/pitchfork.rb +158 -0
- data/pitchfork.gemspec +30 -0
- metadata +137 -0
data/exe/pitchfork
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
require 'pitchfork/launcher'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
ENV["RACK_ENV"] ||= "development"
|
7
|
+
rackup_opts = Pitchfork::Configurator::RACKUP
|
8
|
+
options = rackup_opts[:options]
|
9
|
+
set_no_default_middleware = true
|
10
|
+
|
11
|
+
op = OptionParser.new("", 24, ' ') do |opts|
|
12
|
+
cmd = File.basename($0)
|
13
|
+
opts.banner = "Usage: #{cmd} " \
|
14
|
+
"[ruby options] [#{cmd} options] [rackup config file]"
|
15
|
+
opts.separator "Ruby options:"
|
16
|
+
|
17
|
+
lineno = 1
|
18
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line|
|
19
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
20
|
+
lineno += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
|
24
|
+
$DEBUG = true
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-w", "--warn", "turn warnings on for your script") do
|
28
|
+
$-w = true
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-I", "--include PATH",
|
32
|
+
"specify $LOAD_PATH (may be used more than once)") do |path|
|
33
|
+
$LOAD_PATH.unshift(*path.split(':'))
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-r", "--require LIBRARY",
|
37
|
+
"require the library, before executing your script") do |library|
|
38
|
+
require library
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.separator "#{cmd} options:"
|
42
|
+
|
43
|
+
# some of these switches exist for rackup command-line compatibility,
|
44
|
+
|
45
|
+
opts.on("-o", "--host HOST",
|
46
|
+
"listen on HOST (default: #{Pitchfork::Const::DEFAULT_HOST})") do |h|
|
47
|
+
rackup_opts[:host] = h
|
48
|
+
rackup_opts[:set_listener] = true
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-p", "--port PORT", Integer,
|
52
|
+
"use PORT (default: #{Pitchfork::Const::DEFAULT_PORT})") do |port|
|
53
|
+
rackup_opts[:port] = port
|
54
|
+
rackup_opts[:set_listener] = true
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on("-E", "--env RACK_ENV",
|
58
|
+
"use RACK_ENV for defaults (default: development)") do |e|
|
59
|
+
ENV["RACK_ENV"] = e
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on("-N", "--no-default-middleware",
|
63
|
+
"do not load middleware implied by RACK_ENV") do |e|
|
64
|
+
rackup_opts[:no_default_middleware] = true if set_no_default_middleware
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on("-s", "--server SERVER",
|
68
|
+
"this flag only exists for compatibility") do |s|
|
69
|
+
warn "-s/--server only exists for compatibility with rackup"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Unicorn-specific stuff
|
73
|
+
opts.on("-l", "--listen {HOST:PORT|PATH}",
|
74
|
+
"listen on HOST:PORT or PATH",
|
75
|
+
"this may be specified multiple times",
|
76
|
+
"(default: #{Pitchfork::Const::DEFAULT_LISTEN})") do |address|
|
77
|
+
options[:listeners] << address
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
|
81
|
+
options[:config_file] = f
|
82
|
+
end
|
83
|
+
|
84
|
+
# I'm avoiding Unicorn-specific config options on the command-line.
|
85
|
+
# IMNSHO, config options on the command-line are redundant given
|
86
|
+
# config files and make things unnecessarily complicated with multiple
|
87
|
+
# places to look for a config option.
|
88
|
+
|
89
|
+
opts.separator "Common options:"
|
90
|
+
|
91
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
92
|
+
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on_tail("-v", "--version", "Show version") do
|
97
|
+
puts "#{cmd} v#{Pitchfork::Const::UNICORN_VERSION}"
|
98
|
+
exit
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.parse! ARGV
|
102
|
+
end
|
103
|
+
|
104
|
+
set_no_default_middleware = false
|
105
|
+
app = Pitchfork.builder(ARGV[0] || 'config.ru', op)
|
106
|
+
op = nil
|
107
|
+
|
108
|
+
if $DEBUG
|
109
|
+
require 'pp'
|
110
|
+
pp({
|
111
|
+
:pitchfork_options => options,
|
112
|
+
:app => app,
|
113
|
+
})
|
114
|
+
end
|
115
|
+
|
116
|
+
Pitchfork::HttpServer.new(app, options).start.join
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# CFLAGS used for development (gcc-dependent)
|
2
|
+
# source this file if you want/need them
|
3
|
+
CFLAGS=
|
4
|
+
CFLAGS="$CFLAGS -Wall"
|
5
|
+
CFLAGS="$CFLAGS -Wwrite-strings"
|
6
|
+
CFLAGS="$CFLAGS -Wdeclaration-after-statement"
|
7
|
+
CFLAGS="$CFLAGS -Wcast-qual"
|
8
|
+
CFLAGS="$CFLAGS -Wstrict-prototypes"
|
9
|
+
CFLAGS="$CFLAGS -Wshadow"
|
10
|
+
CFLAGS="$CFLAGS -Wextra"
|
11
|
+
CFLAGS="$CFLAGS -Wno-deprecated-declarations"
|
12
|
+
CFLAGS="$CFLAGS -Waggregate-return"
|
13
|
+
CFLAGS="$CFLAGS -Wchar-subscripts"
|
@@ -0,0 +1,116 @@
|
|
1
|
+
/*
|
2
|
+
* Generic C functions and macros go here, there are no dependencies
|
3
|
+
* on Unicorn internal structures or the Ruby C API in here.
|
4
|
+
*/
|
5
|
+
|
6
|
+
#ifndef UH_util_h
|
7
|
+
#define UH_util_h
|
8
|
+
|
9
|
+
#include <unistd.h>
|
10
|
+
#include <assert.h>
|
11
|
+
#include <limits.h>
|
12
|
+
|
13
|
+
#define MIN(a,b) (a < b ? a : b)
|
14
|
+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
15
|
+
|
16
|
+
#if SIZEOF_OFF_T == SIZEOF_INT
|
17
|
+
# define UH_OFF_T_MAX INT_MAX
|
18
|
+
#elif SIZEOF_OFF_T == SIZEOF_LONG_LONG
|
19
|
+
# define UH_OFF_T_MAX LLONG_MAX
|
20
|
+
#else
|
21
|
+
# error off_t size unknown for this platform!
|
22
|
+
#endif /* SIZEOF_OFF_T check */
|
23
|
+
|
24
|
+
/*
|
25
|
+
* ragel enforces fpc as a const, and merely casting can make picky
|
26
|
+
* compilers unhappy, so we have this little helper do our dirty work
|
27
|
+
*/
|
28
|
+
static inline void *deconst(const void *in)
|
29
|
+
{
|
30
|
+
union { const void *in; void *out; } tmp;
|
31
|
+
|
32
|
+
tmp.in = in;
|
33
|
+
|
34
|
+
return tmp.out;
|
35
|
+
}
|
36
|
+
|
37
|
+
/*
|
38
|
+
* capitalizes all lower-case ASCII characters and converts dashes
|
39
|
+
* to underscores for HTTP headers. Locale-agnostic.
|
40
|
+
*/
|
41
|
+
static void snake_upcase_char(char *c)
|
42
|
+
{
|
43
|
+
if (*c >= 'a' && *c <= 'z')
|
44
|
+
*c &= ~0x20;
|
45
|
+
else if (*c == '-')
|
46
|
+
*c = '_';
|
47
|
+
}
|
48
|
+
|
49
|
+
/* Downcases a single ASCII character. Locale-agnostic. */
|
50
|
+
static void downcase_char(char *c)
|
51
|
+
{
|
52
|
+
if (*c >= 'A' && *c <= 'Z')
|
53
|
+
*c |= 0x20;
|
54
|
+
}
|
55
|
+
|
56
|
+
static int hexchar2int(int xdigit)
|
57
|
+
{
|
58
|
+
if (xdigit >= 'A' && xdigit <= 'F')
|
59
|
+
return xdigit - 'A' + 10;
|
60
|
+
if (xdigit >= 'a' && xdigit <= 'f')
|
61
|
+
return xdigit - 'a' + 10;
|
62
|
+
|
63
|
+
/* Ragel already does runtime range checking for us in Unicorn: */
|
64
|
+
assert(xdigit >= '0' && xdigit <= '9' && "invalid digit character");
|
65
|
+
|
66
|
+
return xdigit - '0';
|
67
|
+
}
|
68
|
+
|
69
|
+
/*
|
70
|
+
* multiplies +i+ by +base+ and increments the result by the parsed
|
71
|
+
* integer value of +xdigit+. +xdigit+ is a character byte
|
72
|
+
* representing a number the range of 0..(base-1)
|
73
|
+
* returns the new value of +i+ on success
|
74
|
+
* returns -1 on errors (including overflow)
|
75
|
+
*/
|
76
|
+
static off_t step_incr(off_t i, int xdigit, const int base)
|
77
|
+
{
|
78
|
+
static const off_t max = UH_OFF_T_MAX;
|
79
|
+
const off_t next_max = (max - (max % base)) / base;
|
80
|
+
off_t offset = hexchar2int(xdigit);
|
81
|
+
|
82
|
+
if (offset > (base - 1))
|
83
|
+
return -1;
|
84
|
+
if (i > next_max)
|
85
|
+
return -1;
|
86
|
+
i *= base;
|
87
|
+
|
88
|
+
if ((offset > (base - 1)) || ((max - i) < offset))
|
89
|
+
return -1;
|
90
|
+
|
91
|
+
return i + offset;
|
92
|
+
}
|
93
|
+
|
94
|
+
/*
|
95
|
+
* parses a non-negative length according to base-10 and
|
96
|
+
* returns it as an off_t value. Returns -1 on errors
|
97
|
+
* (including overflow).
|
98
|
+
*/
|
99
|
+
static off_t parse_length(const char *value, size_t length)
|
100
|
+
{
|
101
|
+
off_t rv;
|
102
|
+
|
103
|
+
for (rv = 0; length-- && rv >= 0; ++value) {
|
104
|
+
if (*value >= '0' && *value <= '9')
|
105
|
+
rv = step_incr(rv, *value, 10);
|
106
|
+
else
|
107
|
+
return -1;
|
108
|
+
}
|
109
|
+
|
110
|
+
return rv;
|
111
|
+
}
|
112
|
+
|
113
|
+
#define CONST_MEM_EQ(const_p, buf, len) \
|
114
|
+
((sizeof(const_p) - 1) == len && !memcmp(const_p, buf, sizeof(const_p) - 1))
|
115
|
+
|
116
|
+
#endif /* UH_util_h */
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
#ifdef HAVE_CONST_PR_SET_CHILD_SUBREAPER
|
3
|
+
#include <sys/prctl.h>
|
4
|
+
|
5
|
+
static VALUE enable_child_subreaper(VALUE module) {
|
6
|
+
if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) {
|
7
|
+
rb_sys_fail("prctl(2) PR_SET_CHILD_SUBREAPER");
|
8
|
+
}
|
9
|
+
return Qtrue;
|
10
|
+
}
|
11
|
+
#else
|
12
|
+
static VALUE enable_child_subreaper(VALUE module) {
|
13
|
+
return Qfalse;
|
14
|
+
}
|
15
|
+
#endif
|
16
|
+
|
17
|
+
void init_child_subreaper(VALUE mPitchfork)
|
18
|
+
{
|
19
|
+
#ifdef HAVE_CONST_PR_SET_CHILD_SUBREAPER
|
20
|
+
rb_define_const(mPitchfork, "CHILD_SUBREAPER_AVAILABLE", Qtrue);
|
21
|
+
#else
|
22
|
+
rb_define_const(mPitchfork, "CHILD_SUBREAPER_AVAILABLE", Qfalse);
|
23
|
+
#endif
|
24
|
+
rb_define_singleton_method(mPitchfork, "enable_child_subreaper", enable_child_subreaper, 0);
|
25
|
+
}
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#ifndef common_field_optimization
|
2
|
+
#define common_field_optimization
|
3
|
+
#include "ruby.h"
|
4
|
+
#include "ruby/encoding.h"
|
5
|
+
#include "c_util.h"
|
6
|
+
|
7
|
+
struct common_field {
|
8
|
+
const signed long len;
|
9
|
+
const char *name;
|
10
|
+
VALUE value;
|
11
|
+
};
|
12
|
+
|
13
|
+
/*
|
14
|
+
* A list of common HTTP headers we expect to receive.
|
15
|
+
* This allows us to avoid repeatedly creating identical string
|
16
|
+
* objects to be used with rb_hash_aset().
|
17
|
+
*/
|
18
|
+
static struct common_field common_http_fields[] = {
|
19
|
+
# define f(N) { (sizeof(N) - 1), N, Qnil }
|
20
|
+
f("ACCEPT"),
|
21
|
+
f("ACCEPT_CHARSET"),
|
22
|
+
f("ACCEPT_ENCODING"),
|
23
|
+
f("ACCEPT_LANGUAGE"),
|
24
|
+
f("ALLOW"),
|
25
|
+
f("AUTHORIZATION"),
|
26
|
+
f("CACHE_CONTROL"),
|
27
|
+
f("CONNECTION"),
|
28
|
+
f("CONTENT_ENCODING"),
|
29
|
+
f("CONTENT_LENGTH"),
|
30
|
+
f("CONTENT_TYPE"),
|
31
|
+
f("COOKIE"),
|
32
|
+
f("DATE"),
|
33
|
+
f("EXPECT"),
|
34
|
+
f("FROM"),
|
35
|
+
f("HOST"),
|
36
|
+
f("IF_MATCH"),
|
37
|
+
f("IF_MODIFIED_SINCE"),
|
38
|
+
f("IF_NONE_MATCH"),
|
39
|
+
f("IF_RANGE"),
|
40
|
+
f("IF_UNMODIFIED_SINCE"),
|
41
|
+
f("KEEP_ALIVE"), /* Firefox sends this */
|
42
|
+
f("MAX_FORWARDS"),
|
43
|
+
f("PRAGMA"),
|
44
|
+
f("PROXY_AUTHORIZATION"),
|
45
|
+
f("RANGE"),
|
46
|
+
f("REFERER"),
|
47
|
+
f("TE"),
|
48
|
+
f("TRAILER"),
|
49
|
+
f("TRANSFER_ENCODING"),
|
50
|
+
f("UPGRADE"),
|
51
|
+
f("USER_AGENT"),
|
52
|
+
f("VIA"),
|
53
|
+
f("X_FORWARDED_FOR"), /* common for proxies */
|
54
|
+
f("X_FORWARDED_PROTO"), /* common for proxies */
|
55
|
+
f("X_REAL_IP"), /* common for proxies */
|
56
|
+
f("WARNING")
|
57
|
+
# undef f
|
58
|
+
};
|
59
|
+
|
60
|
+
#define HTTP_PREFIX "HTTP_"
|
61
|
+
#define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
|
62
|
+
static ID id_uminus;
|
63
|
+
|
64
|
+
#ifdef HAVE_RB_ENC_INTERNED_STR
|
65
|
+
static VALUE str_new_dd_freeze(const char *ptr, long len)
|
66
|
+
{
|
67
|
+
if (RB_ENC_INTERNED_STR_NULL_CHECK && len == 0) {
|
68
|
+
return rb_enc_interned_str("", len, rb_ascii8bit_encoding());
|
69
|
+
} else {
|
70
|
+
return rb_enc_interned_str(ptr, len, rb_ascii8bit_encoding());
|
71
|
+
}
|
72
|
+
}
|
73
|
+
#else
|
74
|
+
static VALUE str_new_dd_freeze(const char *ptr, long len)
|
75
|
+
{
|
76
|
+
VALUE str = rb_str_new(ptr, len);
|
77
|
+
return rb_funcall(str, id_uminus, 0);
|
78
|
+
}
|
79
|
+
#endif
|
80
|
+
|
81
|
+
/* this function is not performance-critical, called only at load time */
|
82
|
+
static void init_common_fields(void)
|
83
|
+
{
|
84
|
+
int i;
|
85
|
+
struct common_field *cf = common_http_fields;
|
86
|
+
char tmp[64];
|
87
|
+
|
88
|
+
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
89
|
+
|
90
|
+
for(i = ARRAY_SIZE(common_http_fields); --i >= 0; cf++) {
|
91
|
+
/* Rack doesn't like certain headers prefixed with "HTTP_" */
|
92
|
+
if (!strcmp("CONTENT_LENGTH", cf->name) ||
|
93
|
+
!strcmp("CONTENT_TYPE", cf->name)) {
|
94
|
+
cf->value = str_new_dd_freeze(cf->name, cf->len);
|
95
|
+
} else {
|
96
|
+
memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
|
97
|
+
cf->value = str_new_dd_freeze(tmp, HTTP_PREFIX_LEN + cf->len);
|
98
|
+
}
|
99
|
+
rb_gc_register_mark_object(cf->value);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
/* this function is called for every header set */
|
104
|
+
static VALUE find_common_field(const char *field, size_t flen)
|
105
|
+
{
|
106
|
+
int i;
|
107
|
+
struct common_field *cf = common_http_fields;
|
108
|
+
|
109
|
+
for(i = ARRAY_SIZE(common_http_fields); --i >= 0; cf++) {
|
110
|
+
if (cf->len == (long)flen && !memcmp(cf->name, field, flen))
|
111
|
+
return cf->value;
|
112
|
+
}
|
113
|
+
return Qnil;
|
114
|
+
}
|
115
|
+
|
116
|
+
/*
|
117
|
+
* We got a strange header that we don't have a memoized value for.
|
118
|
+
* Fallback to creating a new string to use as a hash key.
|
119
|
+
*/
|
120
|
+
static VALUE uncommon_field(const char *field, size_t flen)
|
121
|
+
{
|
122
|
+
VALUE f = rb_str_new(NULL, HTTP_PREFIX_LEN + flen);
|
123
|
+
memcpy(RSTRING_PTR(f), HTTP_PREFIX, HTTP_PREFIX_LEN);
|
124
|
+
memcpy(RSTRING_PTR(f) + HTTP_PREFIX_LEN, field, flen);
|
125
|
+
assert(*(RSTRING_PTR(f) + RSTRING_LEN(f)) == '\0' &&
|
126
|
+
"string didn't end with \\0"); /* paranoia */
|
127
|
+
return f;
|
128
|
+
}
|
129
|
+
|
130
|
+
#endif /* common_field_optimization_h */
|
@@ -0,0 +1,124 @@
|
|
1
|
+
/*
|
2
|
+
* This is only intended for use inside a unicorn worker, nowhere else.
|
3
|
+
* EPOLLEXCLUSIVE somewhat mitigates the thundering herd problem for
|
4
|
+
* mostly idle processes since we can't use blocking accept4.
|
5
|
+
* This is NOT intended for use with multi-threaded servers, nor
|
6
|
+
* single-threaded multi-client ("C10K") servers or anything advanced
|
7
|
+
* like that. This use of epoll is only appropriate for a primitive,
|
8
|
+
* single-client, single-threaded servers like unicorn that need to
|
9
|
+
* support SIGKILL timeouts and parent death detection.
|
10
|
+
*/
|
11
|
+
#if defined(HAVE_EPOLL_CREATE1)
|
12
|
+
# include <sys/epoll.h>
|
13
|
+
# include <errno.h>
|
14
|
+
# include <ruby/io.h>
|
15
|
+
# include <ruby/thread.h>
|
16
|
+
#endif /* __linux__ */
|
17
|
+
|
18
|
+
#if defined(EPOLLEXCLUSIVE) && defined(HAVE_EPOLL_CREATE1)
|
19
|
+
# define USE_EPOLL (1)
|
20
|
+
#else
|
21
|
+
# define USE_EPOLL (0)
|
22
|
+
#endif
|
23
|
+
|
24
|
+
#if USE_EPOLL
|
25
|
+
/*
|
26
|
+
* :nodoc:
|
27
|
+
* returns IO object if EPOLLEXCLUSIVE works and arms readers
|
28
|
+
*/
|
29
|
+
static VALUE prep_readers(VALUE cls, VALUE readers)
|
30
|
+
{
|
31
|
+
long i;
|
32
|
+
int epfd = epoll_create1(EPOLL_CLOEXEC);
|
33
|
+
VALUE epio;
|
34
|
+
|
35
|
+
if (epfd < 0) rb_sys_fail("epoll_create1");
|
36
|
+
|
37
|
+
epio = rb_funcall(cls, rb_intern("for_fd"), 1, INT2NUM(epfd));
|
38
|
+
|
39
|
+
Check_Type(readers, T_ARRAY);
|
40
|
+
for (i = 0; i < RARRAY_LEN(readers); i++) {
|
41
|
+
int rc;
|
42
|
+
struct epoll_event e;
|
43
|
+
rb_io_t *fptr;
|
44
|
+
VALUE io = rb_ary_entry(readers, i);
|
45
|
+
|
46
|
+
e.data.u64 = i; /* the reason readers shouldn't change */
|
47
|
+
|
48
|
+
/*
|
49
|
+
* I wanted to use EPOLLET here, but maintaining our own
|
50
|
+
* equivalent of ep->rdllist in Ruby-space doesn't fit
|
51
|
+
* our design at all (and the kernel already has it's own
|
52
|
+
* code path for doing it). So let the kernel spend
|
53
|
+
* cycles on maintaining level-triggering.
|
54
|
+
*/
|
55
|
+
e.events = EPOLLEXCLUSIVE | EPOLLIN;
|
56
|
+
io = rb_io_get_io(io);
|
57
|
+
GetOpenFile(io, fptr);
|
58
|
+
rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fptr->fd, &e);
|
59
|
+
if (rc < 0) rb_sys_fail("epoll_ctl");
|
60
|
+
}
|
61
|
+
return epio;
|
62
|
+
}
|
63
|
+
#endif /* USE_EPOLL */
|
64
|
+
|
65
|
+
#if USE_EPOLL
|
66
|
+
struct ep_wait {
|
67
|
+
struct epoll_event *events;
|
68
|
+
rb_io_t *fptr;
|
69
|
+
int maxevents;
|
70
|
+
int timeout_msec;
|
71
|
+
};
|
72
|
+
|
73
|
+
static void *do_wait(void *ptr) /* runs w/o GVL */
|
74
|
+
{
|
75
|
+
struct ep_wait *epw = ptr;
|
76
|
+
|
77
|
+
return (void *)(long)epoll_wait(epw->fptr->fd, epw->events,
|
78
|
+
epw->maxevents, epw->timeout_msec);
|
79
|
+
}
|
80
|
+
|
81
|
+
/* :nodoc: */
|
82
|
+
/* readers must not change between prepare_readers and get_readers */
|
83
|
+
static VALUE
|
84
|
+
get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
|
85
|
+
{
|
86
|
+
struct ep_wait epw;
|
87
|
+
long i, n;
|
88
|
+
VALUE buf;
|
89
|
+
|
90
|
+
Check_Type(ready, T_ARRAY);
|
91
|
+
Check_Type(readers, T_ARRAY);
|
92
|
+
epw.maxevents = RARRAY_LENINT(readers);
|
93
|
+
buf = rb_str_buf_new(sizeof(struct epoll_event) * epw.maxevents);
|
94
|
+
epw.events = (struct epoll_event *)RSTRING_PTR(buf);
|
95
|
+
epio = rb_io_get_io(epio);
|
96
|
+
GetOpenFile(epio, epw.fptr);
|
97
|
+
|
98
|
+
epw.timeout_msec = NUM2INT(timeout_msec);
|
99
|
+
n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL);
|
100
|
+
if (n < 0) {
|
101
|
+
if (errno != EINTR) rb_sys_fail("epoll_wait");
|
102
|
+
n = 0;
|
103
|
+
}
|
104
|
+
/* Linux delivers events in order received */
|
105
|
+
for (i = 0; i < n; i++) {
|
106
|
+
struct epoll_event *ev = &epw.events[i];
|
107
|
+
VALUE obj = rb_ary_entry(readers, ev->data.u64);
|
108
|
+
|
109
|
+
if (RTEST(obj))
|
110
|
+
rb_ary_push(ready, obj);
|
111
|
+
}
|
112
|
+
rb_str_resize(buf, 0);
|
113
|
+
return Qfalse;
|
114
|
+
}
|
115
|
+
#endif /* USE_EPOLL */
|
116
|
+
|
117
|
+
static void init_epollexclusive(VALUE mPitchfork)
|
118
|
+
{
|
119
|
+
#if USE_EPOLL
|
120
|
+
VALUE cWaiter = rb_define_class_under(mPitchfork, "Waiter", rb_cIO);
|
121
|
+
rb_define_singleton_method(cWaiter, "prep_readers", prep_readers, 1);
|
122
|
+
rb_define_method(cWaiter, "get_readers", get_readers, 3);
|
123
|
+
#endif
|
124
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#ifndef ext_help_h
|
2
|
+
#define ext_help_h
|
3
|
+
|
4
|
+
/* not all Ruby implementations support frozen objects (Rubinius does not) */
|
5
|
+
#if defined(OBJ_FROZEN)
|
6
|
+
# define assert_frozen(f) assert(OBJ_FROZEN(f) && "unfrozen object")
|
7
|
+
#else
|
8
|
+
# define assert_frozen(f) do {} while (0)
|
9
|
+
#endif /* !defined(OBJ_FROZEN) */
|
10
|
+
|
11
|
+
static inline int str_cstr_eq(VALUE val, const char *ptr, long len)
|
12
|
+
{
|
13
|
+
return (RSTRING_LEN(val) == len && !memcmp(ptr, RSTRING_PTR(val), len));
|
14
|
+
}
|
15
|
+
|
16
|
+
#define STR_CSTR_EQ(val, const_str) \
|
17
|
+
str_cstr_eq(val, const_str, sizeof(const_str) - 1)
|
18
|
+
|
19
|
+
/* strcasecmp isn't locale independent */
|
20
|
+
static int str_cstr_case_eq(VALUE val, const char *ptr, long len)
|
21
|
+
{
|
22
|
+
if (RSTRING_LEN(val) == len) {
|
23
|
+
const char *v = RSTRING_PTR(val);
|
24
|
+
|
25
|
+
for (; len--; ++ptr, ++v) {
|
26
|
+
if ((*ptr == *v) || (*v >= 'A' && *v <= 'Z' && (*v | 0x20) == *ptr))
|
27
|
+
continue;
|
28
|
+
return 0;
|
29
|
+
}
|
30
|
+
return 1;
|
31
|
+
}
|
32
|
+
return 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
#define STR_CSTR_CASE_EQ(val, const_str) \
|
36
|
+
str_cstr_case_eq(val, const_str, sizeof(const_str) - 1)
|
37
|
+
|
38
|
+
#endif /* ext_help_h */
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'mkmf'
|
3
|
+
|
4
|
+
have_const("PR_SET_CHILD_SUBREAPER", "sys/prctl.h")
|
5
|
+
have_func("rb_enc_interned_str", "ruby.h") # Ruby 3.0+
|
6
|
+
if RUBY_VERSION.start_with?('3.0.')
|
7
|
+
# https://bugs.ruby-lang.org/issues/18772
|
8
|
+
$CFLAGS << ' -DRB_ENC_INTERNED_STR_NULL_CHECK=1 '
|
9
|
+
else
|
10
|
+
$CFLAGS << ' -DRB_ENC_INTERNED_STR_NULL_CHECK=0 '
|
11
|
+
end
|
12
|
+
|
13
|
+
have_func('epoll_create1', %w(sys/epoll.h))
|
14
|
+
create_makefile("pitchfork_http")
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#ifndef global_variables_h
|
2
|
+
#define global_variables_h
|
3
|
+
static VALUE eHttpParserError;
|
4
|
+
static VALUE e413;
|
5
|
+
static VALUE e414;
|
6
|
+
|
7
|
+
static VALUE g_rack_url_scheme;
|
8
|
+
static VALUE g_request_method;
|
9
|
+
static VALUE g_request_uri;
|
10
|
+
static VALUE g_fragment;
|
11
|
+
static VALUE g_query_string;
|
12
|
+
static VALUE g_http_version;
|
13
|
+
static VALUE g_request_path;
|
14
|
+
static VALUE g_path_info;
|
15
|
+
static VALUE g_server_name;
|
16
|
+
static VALUE g_server_port;
|
17
|
+
static VALUE g_server_protocol;
|
18
|
+
static VALUE g_http_host;
|
19
|
+
static VALUE g_http_x_forwarded_proto;
|
20
|
+
static VALUE g_http_x_forwarded_ssl;
|
21
|
+
static VALUE g_http_transfer_encoding;
|
22
|
+
static VALUE g_content_length;
|
23
|
+
static VALUE g_http_trailer;
|
24
|
+
static VALUE g_http_connection;
|
25
|
+
static VALUE g_port_80;
|
26
|
+
static VALUE g_port_443;
|
27
|
+
static VALUE g_localhost;
|
28
|
+
static VALUE g_http;
|
29
|
+
static VALUE g_https;
|
30
|
+
static VALUE g_http_09;
|
31
|
+
static VALUE g_http_10;
|
32
|
+
static VALUE g_http_11;
|
33
|
+
|
34
|
+
/** Defines common length and error messages for input length validation. */
|
35
|
+
#define DEF_MAX_LENGTH(N, length) \
|
36
|
+
static const size_t MAX_##N##_LENGTH = length; \
|
37
|
+
static const char * const MAX_##N##_LENGTH_ERR = \
|
38
|
+
"HTTP element " # N " is longer than the " # length " allowed length."
|
39
|
+
|
40
|
+
NORETURN(static void parser_raise(VALUE klass, const char *));
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Validates the max length of given input and throws an HttpParserError
|
44
|
+
* exception if over.
|
45
|
+
*/
|
46
|
+
#define VALIDATE_MAX_LENGTH(len, N) do { \
|
47
|
+
if (len > MAX_##N##_LENGTH) \
|
48
|
+
parser_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \
|
49
|
+
} while (0)
|
50
|
+
|
51
|
+
#define VALIDATE_MAX_URI_LENGTH(len, N) do { \
|
52
|
+
if (len > MAX_##N##_LENGTH) \
|
53
|
+
parser_raise(e414, MAX_##N##_LENGTH_ERR); \
|
54
|
+
} while (0)
|
55
|
+
|
56
|
+
/** Defines global strings in the init method. */
|
57
|
+
#define DEF_GLOBAL(N, val) do { \
|
58
|
+
g_##N = str_new_dd_freeze(val, (long)sizeof(val) - 1); \
|
59
|
+
rb_gc_register_mark_object(g_##N); \
|
60
|
+
} while (0)
|
61
|
+
|
62
|
+
/* Defines the maximum allowed lengths for various input elements.*/
|
63
|
+
DEF_MAX_LENGTH(FIELD_NAME, 256);
|
64
|
+
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
|
65
|
+
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 15);
|
66
|
+
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
67
|
+
DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
|
68
|
+
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
69
|
+
|
70
|
+
static void init_globals(void)
|
71
|
+
{
|
72
|
+
DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
|
73
|
+
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
74
|
+
DEF_GLOBAL(request_uri, "REQUEST_URI");
|
75
|
+
DEF_GLOBAL(fragment, "FRAGMENT");
|
76
|
+
DEF_GLOBAL(query_string, "QUERY_STRING");
|
77
|
+
DEF_GLOBAL(http_version, "HTTP_VERSION");
|
78
|
+
DEF_GLOBAL(request_path, "REQUEST_PATH");
|
79
|
+
DEF_GLOBAL(path_info, "PATH_INFO");
|
80
|
+
DEF_GLOBAL(server_name, "SERVER_NAME");
|
81
|
+
DEF_GLOBAL(server_port, "SERVER_PORT");
|
82
|
+
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
83
|
+
DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO");
|
84
|
+
DEF_GLOBAL(http_x_forwarded_ssl, "HTTP_X_FORWARDED_SSL");
|
85
|
+
DEF_GLOBAL(port_80, "80");
|
86
|
+
DEF_GLOBAL(port_443, "443");
|
87
|
+
DEF_GLOBAL(localhost, "localhost");
|
88
|
+
DEF_GLOBAL(http, "http");
|
89
|
+
DEF_GLOBAL(https, "https");
|
90
|
+
DEF_GLOBAL(http_11, "HTTP/1.1");
|
91
|
+
DEF_GLOBAL(http_10, "HTTP/1.0");
|
92
|
+
DEF_GLOBAL(http_09, "HTTP/0.9");
|
93
|
+
}
|
94
|
+
|
95
|
+
#undef DEF_GLOBAL
|
96
|
+
|
97
|
+
#endif /* global_variables_h */
|