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.

Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.git-blame-ignore-revs +3 -0
  3. data/.gitattributes +5 -0
  4. data/.github/workflows/ci.yml +30 -0
  5. data/.gitignore +23 -0
  6. data/COPYING +674 -0
  7. data/Dockerfile +4 -0
  8. data/Gemfile +9 -0
  9. data/Gemfile.lock +30 -0
  10. data/LICENSE +67 -0
  11. data/README.md +123 -0
  12. data/Rakefile +72 -0
  13. data/docs/Application_Timeouts.md +74 -0
  14. data/docs/CONFIGURATION.md +388 -0
  15. data/docs/DESIGN.md +86 -0
  16. data/docs/FORK_SAFETY.md +80 -0
  17. data/docs/PHILOSOPHY.md +90 -0
  18. data/docs/REFORKING.md +113 -0
  19. data/docs/SIGNALS.md +38 -0
  20. data/docs/TUNING.md +106 -0
  21. data/examples/constant_caches.ru +43 -0
  22. data/examples/echo.ru +25 -0
  23. data/examples/hello.ru +5 -0
  24. data/examples/nginx.conf +156 -0
  25. data/examples/pitchfork.conf.minimal.rb +5 -0
  26. data/examples/pitchfork.conf.rb +77 -0
  27. data/examples/unicorn.socket +11 -0
  28. data/exe/pitchfork +116 -0
  29. data/ext/pitchfork_http/CFLAGS +13 -0
  30. data/ext/pitchfork_http/c_util.h +116 -0
  31. data/ext/pitchfork_http/child_subreaper.h +25 -0
  32. data/ext/pitchfork_http/common_field_optimization.h +130 -0
  33. data/ext/pitchfork_http/epollexclusive.h +124 -0
  34. data/ext/pitchfork_http/ext_help.h +38 -0
  35. data/ext/pitchfork_http/extconf.rb +14 -0
  36. data/ext/pitchfork_http/global_variables.h +97 -0
  37. data/ext/pitchfork_http/httpdate.c +79 -0
  38. data/ext/pitchfork_http/pitchfork_http.c +4318 -0
  39. data/ext/pitchfork_http/pitchfork_http.rl +1024 -0
  40. data/ext/pitchfork_http/pitchfork_http_common.rl +76 -0
  41. data/lib/pitchfork/app/old_rails/static.rb +59 -0
  42. data/lib/pitchfork/children.rb +124 -0
  43. data/lib/pitchfork/configurator.rb +314 -0
  44. data/lib/pitchfork/const.rb +23 -0
  45. data/lib/pitchfork/http_parser.rb +206 -0
  46. data/lib/pitchfork/http_response.rb +63 -0
  47. data/lib/pitchfork/http_server.rb +822 -0
  48. data/lib/pitchfork/launcher.rb +9 -0
  49. data/lib/pitchfork/mem_info.rb +36 -0
  50. data/lib/pitchfork/message.rb +130 -0
  51. data/lib/pitchfork/mold_selector.rb +29 -0
  52. data/lib/pitchfork/preread_input.rb +33 -0
  53. data/lib/pitchfork/refork_condition.rb +21 -0
  54. data/lib/pitchfork/select_waiter.rb +9 -0
  55. data/lib/pitchfork/socket_helper.rb +199 -0
  56. data/lib/pitchfork/stream_input.rb +152 -0
  57. data/lib/pitchfork/tee_input.rb +133 -0
  58. data/lib/pitchfork/tmpio.rb +35 -0
  59. data/lib/pitchfork/version.rb +8 -0
  60. data/lib/pitchfork/worker.rb +244 -0
  61. data/lib/pitchfork.rb +158 -0
  62. data/pitchfork.gemspec +30 -0
  63. 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 */