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
@@ -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'