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