pitchfork 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,76 @@
1
+ %%{
2
+
3
+ machine pitchfork_http_common;
4
+
5
+ #### HTTP PROTOCOL GRAMMAR
6
+ # line endings
7
+ CRLF = ("\r\n" | "\n");
8
+
9
+ # character types
10
+ CTL = (cntrl | 127);
11
+ safe = ("$" | "-" | "_" | ".");
12
+ extra = ("!" | "*" | "'" | "(" | ")" | ",");
13
+ reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
14
+ sorta_safe = ("\"" | "<" | ">");
15
+ unsafe = (CTL | " " | "#" | "%" | sorta_safe);
16
+ national = any -- (alpha | digit | reserved | extra | safe | unsafe);
17
+ unreserved = (alpha | digit | safe | extra | national);
18
+ escape = ("%" xdigit xdigit);
19
+ uchar = (unreserved | escape | sorta_safe);
20
+ pchar = (uchar | ":" | "@" | "&" | "=" | "+");
21
+ tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
22
+ lws = (" " | "\t");
23
+ content = ((any -- CTL) | lws);
24
+
25
+ # elements
26
+ token = (ascii -- (CTL | tspecials));
27
+
28
+ # URI schemes and absolute paths
29
+ scheme = ( "http"i ("s"i)? ) $downcase_char >mark %scheme;
30
+ hostname = ((alnum | "-" | "." | "_")+ | ("[" (":" | xdigit)+ "]"));
31
+ host_with_port = (hostname (":" digit*)?) >mark %host;
32
+ userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*;
33
+
34
+ path = ( pchar+ ( "/" pchar* )* ) ;
35
+ query = ( uchar | reserved )* %query_string ;
36
+ param = ( pchar | "/" )* ;
37
+ params = ( param ( ";" param )* ) ;
38
+ rel_path = (path? (";" params)? %request_path) ("?" %start_query query)?;
39
+ absolute_path = ( "/"+ rel_path );
40
+ path_uri = absolute_path > mark %request_uri;
41
+ Absolute_URI = (scheme "://" userinfo host_with_port path_uri);
42
+
43
+ Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
44
+ Fragment = ( uchar | reserved )* >mark %fragment;
45
+ Method = (token){1,20} >mark %request_method;
46
+ GetOnly = "GET" >mark %request_method;
47
+
48
+ http_number = ( digit+ "." digit+ ) ;
49
+ HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
50
+ Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;
51
+
52
+ field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field;
53
+
54
+ field_value = content* >start_value %write_value;
55
+
56
+ value_cont = lws+ content* >start_value %write_cont_value;
57
+
58
+ message_header = ((field_name ":" lws* field_value)|value_cont) :> CRLF;
59
+ chunk_ext_val = token*;
60
+ chunk_ext_name = token*;
61
+ chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
62
+ last_chunk = "0"+ chunk_extension CRLF;
63
+ chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
64
+ chunk_end = CRLF;
65
+ chunk_body = any >skip_chunk_data;
66
+ chunk_begin = chunk_size chunk_extension CRLF;
67
+ chunk = chunk_begin chunk_body chunk_end;
68
+ ChunkedBody := chunk* last_chunk @end_chunked_body;
69
+ Trailers := (message_header)* CRLF @end_trailers;
70
+
71
+ FullRequest = Request_Line (message_header)* CRLF @header_done;
72
+ SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done;
73
+
74
+ main := FullRequest | SimpleRequest;
75
+
76
+ }%%
@@ -0,0 +1,59 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # This code is based on the original Rails handler in Mongrel
4
+ # Copyright (c) 2005 Zed A. Shaw
5
+ # Copyright (c) 2009 Eric Wong
6
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
7
+ # the GPLv3
8
+
9
+ # Static file handler for Rails < 2.3. This handler is only provided
10
+ # as a convenience for developers. Performance-minded deployments should
11
+ # use nginx (or similar) for serving static files.
12
+ #
13
+ # This supports page caching directly and will try to resolve a
14
+ # request in the following order:
15
+ #
16
+ # * If the requested exact PATH_INFO exists as a file then serve it.
17
+ # * If it exists at PATH_INFO+rest_operator+".html" exists
18
+ # then serve that.
19
+ #
20
+ # This means that if you are using page caching it will actually work
21
+ # with Pitchfork and you should see a decent speed boost (but not as
22
+ # fast as if you use a static server like nginx).
23
+ class Pitchfork::App::OldRails::Static < Struct.new(:app, :root, :file_server)
24
+ FILE_METHODS = { 'GET' => true, 'HEAD' => true }
25
+
26
+ # avoid allocating new strings for hash lookups
27
+ REQUEST_METHOD = 'REQUEST_METHOD'
28
+ REQUEST_URI = 'REQUEST_URI'
29
+ PATH_INFO = 'PATH_INFO'
30
+
31
+ def initialize(app)
32
+ self.app = app
33
+ self.root = "#{::RAILS_ROOT}/public"
34
+ self.file_server = ::Rack::File.new(root)
35
+ end
36
+
37
+ def call(env)
38
+ # short circuit this ASAP if serving non-file methods
39
+ FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env)
40
+
41
+ # first try the path as-is
42
+ path_info = env[PATH_INFO].chomp("/")
43
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
44
+ # File exists as-is so serve it up
45
+ env[PATH_INFO] = path_info
46
+ return file_server.call(env)
47
+ end
48
+
49
+ # then try the cached version:
50
+ path_info << ActionController::Base.page_cache_extension
51
+
52
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
53
+ env[PATH_INFO] = path_info
54
+ return file_server.call(env)
55
+ end
56
+
57
+ app.call(env) # call OldRails
58
+ end
59
+ end if defined?(Pitchfork::App::OldRails)
@@ -0,0 +1,124 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module Pitchfork
4
+ # This class keep tracks of the state of all the master children.
5
+ class Children
6
+ attr_reader :mold
7
+ attr_accessor :last_generation
8
+
9
+ def initialize
10
+ @last_generation = 0
11
+ @children = {} # All children, including molds, indexed by PID.
12
+ @workers = {} # Workers indexed by their `nr`.
13
+ @molds = {} # Molds, index by PID.
14
+ @mold = nil # The latest mold, if any.
15
+ @pending_workers = {} # Pending workers indexed by their `nr`.
16
+ @pending_molds = {} # Worker promoted to mold, not yet acknowledged
17
+ end
18
+
19
+ def refresh
20
+ @workers.each_value(&:refresh)
21
+ @molds.each_value(&:refresh)
22
+ end
23
+
24
+ def register(child)
25
+ # Children always start as workers, never molds, so we know they have a `#nr`.
26
+ @pending_workers[child.nr] = @workers[child.nr] = child
27
+ end
28
+
29
+ def register_mold(mold)
30
+ @pending_molds[mold.pid] = mold
31
+ @children[mold.pid] = mold
32
+ @mold = mold
33
+ end
34
+
35
+ def fetch(pid)
36
+ @children.fetch(pid)
37
+ end
38
+
39
+ def update(message)
40
+ child = @children[message.pid] || (message.nr && @workers[message.nr])
41
+ old_nr = child.nr
42
+
43
+ child.update(message)
44
+
45
+ if child.mold?
46
+ @workers.delete(old_nr)
47
+ @pending_molds.delete(child.pid)
48
+ @molds[child.pid] = child
49
+ @mold = child
50
+ end
51
+ if child.pid
52
+ @children[child.pid] = child
53
+ @pending_workers.delete(child.nr)
54
+ end
55
+ child
56
+ end
57
+
58
+ def nr_alive?(nr)
59
+ @workers.key?(nr)
60
+ end
61
+
62
+ def reap(pid)
63
+ if child = @children.delete(pid)
64
+ @pending_workers.delete(child.nr)
65
+ @pending_molds.delete(child.pid)
66
+ @molds.delete(child.pid)
67
+ @workers.delete(child.nr)
68
+ if @mold == child
69
+ @mold = nil
70
+ end
71
+ end
72
+ child
73
+ end
74
+
75
+ def promote(worker)
76
+ @pending_molds[worker.pid] = worker
77
+ worker.promote(self.last_generation += 1)
78
+ end
79
+
80
+ def pending_workers?
81
+ !(@pending_workers.empty? && @pending_molds.empty?)
82
+ end
83
+
84
+ def pending_promotion?
85
+ !@pending_molds.empty?
86
+ end
87
+
88
+ def molds
89
+ @molds.values
90
+ end
91
+
92
+ def each(&block)
93
+ @children.each_value(&block)
94
+ end
95
+
96
+ def each_worker(&block)
97
+ @workers.each_value(&block)
98
+ end
99
+
100
+ def workers
101
+ @workers.values
102
+ end
103
+
104
+ def fresh_workers
105
+ if @mold
106
+ workers.select { |w| w.generation >= @mold.generation }
107
+ else
108
+ workers
109
+ end
110
+ end
111
+
112
+ def workers_count
113
+ @workers.size
114
+ end
115
+
116
+ def total_pss
117
+ total_pss = MemInfo.new(Process.pid).pss
118
+ @children.each do |_, worker|
119
+ total_pss += worker.meminfo.pss if worker.meminfo
120
+ end
121
+ total_pss
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,314 @@
1
+ # -*- encoding: binary -*-
2
+ require 'logger'
3
+
4
+ module Pitchfork
5
+ # Implements a simple DSL for configuring a pitchfork server.
6
+ #
7
+ # See https://github.com/Shopify/pitchfork/tree/master/examples/pitchfork.conf.rb and
8
+ # https://github.com/Shopify/pitchfork/tree/master/examples/pitchfork.conf.minimal.rb
9
+ # example configuration files.
10
+ #
11
+ # See the docs/TUNING.md document for more information on tuning pitchfork.
12
+ class Configurator
13
+ include Pitchfork
14
+
15
+ # :stopdoc:
16
+ attr_accessor :set, :config_file, :after_load
17
+
18
+ # used to stash stuff for deferred processing of cli options in
19
+ # config.ru. Do not rely on
20
+ # this being around later on...
21
+ RACKUP = {
22
+ :host => Pitchfork::Const::DEFAULT_HOST,
23
+ :port => Pitchfork::Const::DEFAULT_PORT,
24
+ :set_listener => false,
25
+ :options => { :listeners => [] }
26
+ }
27
+
28
+ # Default settings for Pitchfork
29
+ DEFAULTS = {
30
+ :timeout => 20,
31
+ :logger => Logger.new($stderr),
32
+ :worker_processes => 1,
33
+ :after_fork => lambda { |server, worker|
34
+ server.logger.info("worker=#{worker.nr} gen=#{worker.generation} pid=#{$$} spawned")
35
+ },
36
+ :before_fork => lambda { |server, worker|
37
+ server.logger.info("worker=#{worker.nr} gen=#{worker.generation} spawning...")
38
+ },
39
+ :after_worker_exit => lambda { |server, worker, status|
40
+ m = if worker.nil?
41
+ "repead unknown process (#{status.inspect})"
42
+ elsif worker.mold?
43
+ "mold pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
44
+ else
45
+ "worker=#{worker.nr rescue 'unknown'} pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
46
+ end
47
+ if status.success?
48
+ server.logger.info(m)
49
+ else
50
+ server.logger.error(m)
51
+ end
52
+ },
53
+ :after_worker_ready => lambda { |server, worker|
54
+ server.logger.info("worker=#{worker.nr} ready")
55
+ },
56
+ :early_hints => false,
57
+ :mold_selector => MoldSelector::LeastSharedMemory.new,
58
+ :refork_condition => nil,
59
+ :check_client_connection => false,
60
+ :rewindable_input => true,
61
+ :client_body_buffer_size => Pitchfork::Const::MAX_BODY,
62
+ }
63
+ #:startdoc:
64
+
65
+ def initialize(defaults = {}) #:nodoc:
66
+ self.set = Hash.new(:unset)
67
+ @use_defaults = defaults.delete(:use_defaults)
68
+ self.config_file = defaults.delete(:config_file)
69
+
70
+ set.merge!(DEFAULTS) if @use_defaults
71
+ defaults.each { |key, value| self.__send__(key, value) }
72
+ Hash === set[:listener_opts] or
73
+ set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
74
+ Array === set[:listeners] or set[:listeners] = []
75
+ load(false)
76
+ end
77
+
78
+ def load(merge_defaults = true) #:nodoc:
79
+ if merge_defaults && @use_defaults
80
+ set.merge!(DEFAULTS) if @use_defaults
81
+ end
82
+ instance_eval(File.read(config_file), config_file) if config_file
83
+
84
+ parse_rackup_file
85
+
86
+ RACKUP[:set_listener] and
87
+ set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
88
+
89
+ RACKUP[:no_default_middleware] and
90
+ set[:default_middleware] = false
91
+ end
92
+
93
+ def commit!(server, options = {}) #:nodoc:
94
+ skip = options[:skip] || []
95
+ if ready_pipe = RACKUP.delete(:ready_pipe)
96
+ server.ready_pipe = ready_pipe
97
+ end
98
+ if set[:check_client_connection]
99
+ set[:listeners].each do |address|
100
+ if set[:listener_opts][address][:tcp_nopush] == true
101
+ raise ArgumentError,
102
+ "check_client_connection is incompatible with tcp_nopush:true"
103
+ end
104
+ end
105
+ end
106
+ set.each do |key, value|
107
+ value == :unset and next
108
+ skip.include?(key) and next
109
+ server.__send__("#{key}=", value)
110
+ end
111
+ end
112
+
113
+ def [](key) # :nodoc:
114
+ set[key]
115
+ end
116
+
117
+ def logger(obj)
118
+ %w(debug info warn error fatal).each do |m|
119
+ obj.respond_to?(m) and next
120
+ raise ArgumentError, "logger=#{obj} does not respond to method=#{m}"
121
+ end
122
+
123
+ set[:logger] = obj
124
+ end
125
+
126
+ def before_fork(*args, &block)
127
+ set_hook(:before_fork, block_given? ? block : args[0])
128
+ end
129
+
130
+ def after_fork(*args, &block)
131
+ set_hook(:after_fork, block_given? ? block : args[0])
132
+ end
133
+
134
+ def after_worker_ready(*args, &block)
135
+ set_hook(:after_worker_ready, block_given? ? block : args[0])
136
+ end
137
+
138
+ def after_worker_exit(*args, &block)
139
+ set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
140
+ end
141
+
142
+ def mold_selector(*args, &block)
143
+ set_hook(:mold_selector, block_given? ? block : args[0], 3)
144
+ end
145
+
146
+ def timeout(seconds)
147
+ set_int(:timeout, seconds, 3)
148
+ # POSIX says 31 days is the smallest allowed maximum timeout for select()
149
+ max = 30 * 60 * 60 * 24
150
+ set[:timeout] = seconds > max ? max : seconds
151
+ end
152
+
153
+ def worker_processes(nr)
154
+ set_int(:worker_processes, nr, 1)
155
+ end
156
+
157
+ def default_middleware(bool)
158
+ set_bool(:default_middleware, bool)
159
+ end
160
+
161
+ def early_hints(bool)
162
+ set_bool(:early_hints, bool)
163
+ end
164
+
165
+ # sets listeners to the given +addresses+, replacing or augmenting the
166
+ # current set.
167
+ def listeners(addresses) # :nodoc:
168
+ Array === addresses or addresses = Array(addresses)
169
+ addresses.map! { |addr| expand_addr(addr) }
170
+ set[:listeners] = addresses
171
+ end
172
+
173
+ def listen(address, options = {})
174
+ address = expand_addr(address)
175
+ if String === address
176
+ [ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
177
+ value = options[key] or next
178
+ Integer === value or
179
+ raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
180
+ end
181
+ [ :tcp_nodelay, :tcp_nopush, :ipv6only, :reuseport ].each do |key|
182
+ (value = options[key]).nil? and next
183
+ TrueClass === value || FalseClass === value or
184
+ raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
185
+ end
186
+ unless (value = options[:delay]).nil?
187
+ Numeric === value or
188
+ raise ArgumentError, "not numeric: delay=#{value.inspect}"
189
+ end
190
+ set[:listener_opts][address].merge!(options)
191
+ end
192
+
193
+ set[:listeners] << address
194
+ end
195
+
196
+ def rewindable_input(bool)
197
+ set_bool(:rewindable_input, bool)
198
+ end
199
+
200
+ def client_body_buffer_size(bytes)
201
+ set_int(:client_body_buffer_size, bytes, 0)
202
+ end
203
+
204
+ def check_client_connection(bool)
205
+ set_bool(:check_client_connection, bool)
206
+ end
207
+
208
+ # Defines the number of requests per-worker after which a new generation
209
+ # should be spawned.
210
+ #
211
+ # example:
212
+ #. refork_after [50, 100, 1000]
213
+ #
214
+ # Note that reforking is only available on Linux. Other Unix-like systems
215
+ # don't have this capability.
216
+ def refork_after(limits)
217
+ set[:refork_condition] = ReforkCondition::RequestsCount.new(limits)
218
+ end
219
+
220
+ # expands "unix:path/to/foo" to a socket relative to the current path
221
+ # expands pathnames of sockets if relative to "~" or "~username"
222
+ # expands "*:port and ":port" to "0.0.0.0:port"
223
+ def expand_addr(address) #:nodoc:
224
+ return "0.0.0.0:#{address}" if Integer === address
225
+ return address unless String === address
226
+
227
+ case address
228
+ when %r{\Aunix:(.*)\z}
229
+ File.expand_path($1)
230
+ when %r{\A~}
231
+ File.expand_path(address)
232
+ when %r{\A(?:\*:)?(\d+)\z}
233
+ "0.0.0.0:#$1"
234
+ when %r{\A\[([a-fA-F0-9:]+)\]\z}, %r/\A((?:\d+\.){3}\d+)\z/
235
+ canonicalize_tcp($1, 80)
236
+ when %r{\A\[([a-fA-F0-9:]+)\]:(\d+)\z}, %r{\A(.*):(\d+)\z}
237
+ canonicalize_tcp($1, $2.to_i)
238
+ else
239
+ address
240
+ end
241
+ end
242
+
243
+ private
244
+ def set_int(var, n, min) #:nodoc:
245
+ Integer === n or raise ArgumentError, "not an integer: #{var}=#{n.inspect}"
246
+ n >= min or raise ArgumentError, "too low (< #{min}): #{var}=#{n.inspect}"
247
+ set[var] = n
248
+ end
249
+
250
+ def canonicalize_tcp(addr, port)
251
+ packed = Socket.pack_sockaddr_in(port, addr)
252
+ port, addr = Socket.unpack_sockaddr_in(packed)
253
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
254
+ end
255
+
256
+ def set_path(var, path) #:nodoc:
257
+ case path
258
+ when NilClass, String
259
+ set[var] = path
260
+ else
261
+ raise ArgumentError
262
+ end
263
+ end
264
+
265
+ def check_bool(var, bool) # :nodoc:
266
+ case bool
267
+ when true, false
268
+ return bool
269
+ end
270
+ raise ArgumentError, "#{var}=#{bool.inspect} not a boolean"
271
+ end
272
+
273
+ def set_bool(var, bool) #:nodoc:
274
+ set[var] = check_bool(var, bool)
275
+ end
276
+
277
+ def set_hook(var, my_proc, req_arity = 2) #:nodoc:
278
+ case my_proc
279
+ when Proc
280
+ arity = my_proc.arity
281
+ (arity == req_arity) or \
282
+ raise ArgumentError,
283
+ "#{var}=#{my_proc.inspect} has invalid arity: " \
284
+ "#{arity} (need #{req_arity})"
285
+ when NilClass
286
+ my_proc = DEFAULTS[var]
287
+ else
288
+ raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}"
289
+ end
290
+ set[var] = my_proc
291
+ end
292
+
293
+ # This only parses the embedded switches in .ru files
294
+ # (for "rackup" compatibility)
295
+ def parse_rackup_file # :nodoc:
296
+ ru = RACKUP[:file] or return # we only return here in unit tests
297
+
298
+ # :rails means use (old) Rails autodetect
299
+ if ru == :rails
300
+ File.readable?('config.ru') or return
301
+ ru = 'config.ru'
302
+ end
303
+
304
+ File.readable?(ru) or
305
+ raise ArgumentError, "rackup file (#{ru}) not readable"
306
+
307
+ # it could be a .rb file, too, we don't parse those manually
308
+ ru.end_with?('.ru') or return
309
+
310
+ /^#\\(.*)/ =~ File.read(ru) or return
311
+ RACKUP[:optparse].parse!($1.split(/\s+/))
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module Pitchfork
4
+ module Const # :nodoc:
5
+ # default TCP listen host address (0.0.0.0, all interfaces)
6
+ DEFAULT_HOST = "0.0.0.0"
7
+
8
+ # default TCP listen port (8080)
9
+ DEFAULT_PORT = 8080
10
+
11
+ # default TCP listen address and port (0.0.0.0:8080)
12
+ DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}"
13
+
14
+ # The basic request body size we'll try to read at once (16 kilobytes).
15
+ CHUNK_SIZE = 16 * 1024
16
+
17
+ # Maximum request body size before it is moved out of memory and into a
18
+ # temporary file for reading (112 kilobytes). This is the default
19
+ # value of client_body_buffer_size.
20
+ MAX_BODY = 1024 * 112
21
+ end
22
+ end
23
+ require_relative 'version'