mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Write a bunch of documentation
This commit is contained in:
parent
6d1d60abfa
commit
b18cfae4b1
6 changed files with 153 additions and 10 deletions
|
@ -7,10 +7,18 @@ require 'puma/const'
|
||||||
require 'rack/commonlogger'
|
require 'rack/commonlogger'
|
||||||
|
|
||||||
module Puma
|
module Puma
|
||||||
|
# Handles invoke a Puma::Server in a command line style.
|
||||||
|
#
|
||||||
class CLI
|
class CLI
|
||||||
DefaultTCPHost = "0.0.0.0"
|
DefaultTCPHost = "0.0.0.0"
|
||||||
DefaultTCPPort = 9292
|
DefaultTCPPort = 9292
|
||||||
|
|
||||||
|
# Create a new CLI object using +argv+ as the command line
|
||||||
|
# arguments.
|
||||||
|
#
|
||||||
|
# +stdout+ and +stderr+ can be set to IO-like objects which
|
||||||
|
# this object will report status on.
|
||||||
|
#
|
||||||
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
||||||
@argv = argv
|
@argv = argv
|
||||||
@stdout = stdout
|
@stdout = stdout
|
||||||
|
@ -21,15 +29,21 @@ module Puma
|
||||||
setup_options
|
setup_options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Write +str+ to +@stdout+
|
||||||
|
#
|
||||||
def log(str)
|
def log(str)
|
||||||
@stdout.puts str
|
@stdout.puts str
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Write +str+ to +@stderr+
|
||||||
|
#
|
||||||
def error(str)
|
def error(str)
|
||||||
@stderr.puts "ERROR: #{str}"
|
@stderr.puts "ERROR: #{str}"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build the OptionParser object to handle the available options.
|
||||||
|
#
|
||||||
def setup_options
|
def setup_options
|
||||||
@options = {
|
@options = {
|
||||||
:min_threads => 0,
|
:min_threads => 0,
|
||||||
|
@ -73,6 +87,9 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Load the specified rackup file, pull an options from
|
||||||
|
# the rackup file, and set @app.
|
||||||
|
#
|
||||||
def load_rackup
|
def load_rackup
|
||||||
@app, options = Rack::Builder.parse_file @rackup
|
@app, options = Rack::Builder.parse_file @rackup
|
||||||
@options.merge! options
|
@options.merge! options
|
||||||
|
@ -84,6 +101,9 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# If configured, write the pid of the current process out
|
||||||
|
# to a file.
|
||||||
|
#
|
||||||
def write_pid
|
def write_pid
|
||||||
if path = @options[:pidfile]
|
if path = @options[:pidfile]
|
||||||
File.open(path, "w") do |f|
|
File.open(path, "w") do |f|
|
||||||
|
@ -92,10 +112,14 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
def parse_options
|
def parse_options
|
||||||
@parser.parse! @argv
|
@parser.parse! @argv
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse the options, load the rackup, start the server and wait
|
||||||
|
# for it to finish.
|
||||||
|
#
|
||||||
def run
|
def run
|
||||||
parse_options
|
parse_options
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,17 @@ require 'puma/const'
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
|
|
||||||
module Puma
|
module Puma
|
||||||
|
# The default implement of an event sink object used by Server
|
||||||
|
# for when certain kinds of events occur in the life of the server.
|
||||||
|
#
|
||||||
|
# The methods available are the events that the Server fires.
|
||||||
|
#
|
||||||
class Events
|
class Events
|
||||||
|
|
||||||
include Const
|
include Const
|
||||||
|
|
||||||
|
# Create an Events object that prints to +stdout+ and +stderr+.
|
||||||
|
#
|
||||||
def initialize(stdout, stderr)
|
def initialize(stdout, stderr)
|
||||||
@stdout = stdout
|
@stdout = stdout
|
||||||
@stderr = stderr
|
@stderr = stderr
|
||||||
|
@ -13,11 +20,19 @@ module Puma
|
||||||
|
|
||||||
attr_reader :stdout, :stderr
|
attr_reader :stdout, :stderr
|
||||||
|
|
||||||
|
# An HTTP parse error has occured.
|
||||||
|
# +server+ is the Server object, +env+ the request, and +error+ a
|
||||||
|
# parsing exception.
|
||||||
|
#
|
||||||
def parse_error(server, env, error)
|
def parse_error(server, env, error)
|
||||||
@stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}"
|
@stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}"
|
||||||
@stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
|
@stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# An unknown error has occured.
|
||||||
|
# +server+ is the Server object, +env+ the request, +error+ an exception
|
||||||
|
# object, and +kind+ some additional info.
|
||||||
|
#
|
||||||
def unknown_error(server, env, error, kind="Unknown")
|
def unknown_error(server, env, error, kind="Unknown")
|
||||||
if error.respond_to? :render
|
if error.respond_to? :render
|
||||||
error.render "#{Time.now}: #{kind} error", @stderr
|
error.render "#{Time.now}: #{kind} error", @stderr
|
||||||
|
@ -29,6 +44,9 @@ module Puma
|
||||||
|
|
||||||
DEFAULT = new(STDOUT, STDERR)
|
DEFAULT = new(STDOUT, STDERR)
|
||||||
|
|
||||||
|
# Returns an Events object which writes it's status to 2 StringIO
|
||||||
|
# objects.
|
||||||
|
#
|
||||||
def self.strings
|
def self.strings
|
||||||
Events.new StringIO.new, StringIO.new
|
Events.new StringIO.new, StringIO.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
module Puma
|
module Puma
|
||||||
|
|
||||||
|
# Provides an IO-like object that always appears to contain no data.
|
||||||
|
# Used as the value for rack.input when the request has no body.
|
||||||
|
#
|
||||||
class NullIO
|
class NullIO
|
||||||
|
|
||||||
|
# Always returns nil
|
||||||
|
#
|
||||||
def gets
|
def gets
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Never yields
|
||||||
|
#
|
||||||
def each
|
def each
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Always returns nil
|
||||||
|
#
|
||||||
def read(count)
|
def read(count)
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Does nothing
|
||||||
|
#
|
||||||
def rewind
|
def rewind
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Does nothing
|
||||||
|
#
|
||||||
def close
|
def close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
require 'rack/commonlogger'
|
require 'rack/commonlogger'
|
||||||
|
|
||||||
module Rack
|
module Rack
|
||||||
# Patch CommonLogger to use after_reply
|
# Patch CommonLogger to use after_reply.
|
||||||
|
#
|
||||||
|
# Simply request this file and CommonLogger will be a bit more
|
||||||
|
# efficient.
|
||||||
class CommonLogger
|
class CommonLogger
|
||||||
remove_method :call
|
remove_method :call
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ require 'puma_http11'
|
||||||
require 'socket'
|
require 'socket'
|
||||||
|
|
||||||
module Puma
|
module Puma
|
||||||
|
|
||||||
|
# The HTTP Server itself. Serves out a single Rack app.
|
||||||
class Server
|
class Server
|
||||||
|
|
||||||
include Puma::Const
|
include Puma::Const
|
||||||
|
@ -24,11 +26,13 @@ module Puma
|
||||||
attr_accessor :max_threads
|
attr_accessor :max_threads
|
||||||
attr_accessor :persistent_timeout
|
attr_accessor :persistent_timeout
|
||||||
|
|
||||||
# Creates a working server on host:port (strange things happen if port
|
# Create a server for the rack app +app+.
|
||||||
# isn't a Number).
|
|
||||||
#
|
#
|
||||||
# Use HttpServer#run to start the server and HttpServer#acceptor.join to
|
# +events+ is an object which will be called when certain error events occur
|
||||||
# join the thread that's processing incoming requests on the socket.
|
# to be handled. See Puma::Events for the list of current methods to implement.
|
||||||
|
#
|
||||||
|
# Server#run returns a thread that you can join on to wait for the server
|
||||||
|
# to do it's work.
|
||||||
#
|
#
|
||||||
def initialize(app, events=Events::DEFAULT)
|
def initialize(app, events=Events::DEFAULT)
|
||||||
@app = app
|
@app = app
|
||||||
|
@ -62,6 +66,9 @@ module Puma
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
||||||
|
# packetizes our stream. This improves both latency and throughput.
|
||||||
|
#
|
||||||
if RUBY_PLATFORM =~ /linux/
|
if RUBY_PLATFORM =~ /linux/
|
||||||
# 6 == Socket::IPPROTO_TCP
|
# 6 == Socket::IPPROTO_TCP
|
||||||
# 3 == TCP_CORK
|
# 3 == TCP_CORK
|
||||||
|
@ -81,6 +88,10 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tell the server to listen on host +host+, port +port+.
|
||||||
|
# If optimize_for_latency is true (the default) then clients connecting
|
||||||
|
# will be optimized for latency over throughput.
|
||||||
|
#
|
||||||
def add_tcp_listener(host, port, optimize_for_latency=true)
|
def add_tcp_listener(host, port, optimize_for_latency=true)
|
||||||
s = TCPServer.new(host, port)
|
s = TCPServer.new(host, port)
|
||||||
if optimize_for_latency
|
if optimize_for_latency
|
||||||
|
@ -89,13 +100,15 @@ module Puma
|
||||||
@ios << s
|
@ios << s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
||||||
|
#
|
||||||
def add_unix_listener(path)
|
def add_unix_listener(path)
|
||||||
@ios << UNIXServer.new(path)
|
@ios << UNIXServer.new(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Runs the server. It returns the thread used so you can "join" it.
|
# Runs the server. It returns the thread used so you can join it.
|
||||||
# You can also access the HttpServer#acceptor attribute to get the
|
# The thread is always available via #thread to be join'd
|
||||||
# thread later.
|
#
|
||||||
def run
|
def run
|
||||||
BasicSocket.do_not_reverse_lookup = true
|
BasicSocket.do_not_reverse_lookup = true
|
||||||
|
|
||||||
|
@ -137,6 +150,7 @@ module Puma
|
||||||
return @thread
|
return @thread
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
def handle_check
|
def handle_check
|
||||||
cmd = @check.read(1)
|
cmd = @check.read(1)
|
||||||
|
|
||||||
|
@ -149,6 +163,12 @@ module Puma
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Given a connection on +client+, handle the incoming requests.
|
||||||
|
#
|
||||||
|
# This method support HTTP Keep-Alive so it may, depending on if the client
|
||||||
|
# indicates that it supports keep alive, wait for another request before
|
||||||
|
# returning.
|
||||||
|
#
|
||||||
def process_client(client)
|
def process_client(client)
|
||||||
parser = HttpParser.new
|
parser = HttpParser.new
|
||||||
|
|
||||||
|
@ -198,12 +218,16 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue EOFError, SystemCallError
|
|
||||||
client.close rescue nil
|
|
||||||
|
|
||||||
|
# The client disconnected while we were reading data
|
||||||
|
rescue EOFError, SystemCallError
|
||||||
|
# Swallow them. The ensure tries to close +client+ down
|
||||||
|
|
||||||
|
# The client doesn't know HTTP well
|
||||||
rescue HttpParserError => e
|
rescue HttpParserError => e
|
||||||
@events.parse_error self, env, e
|
@events.parse_error self, env, e
|
||||||
|
|
||||||
|
# Server error
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
@events.unknown_error self, env, e, "Read"
|
@events.unknown_error self, env, e, "Read"
|
||||||
|
|
||||||
|
@ -218,6 +242,9 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Given a Hash +env+ for the request read from +client+, add
|
||||||
|
# and fixup keys to comply with Rack's env guidelines.
|
||||||
|
#
|
||||||
def normalize_env(env, client)
|
def normalize_env(env, client)
|
||||||
if host = env[HTTP_HOST]
|
if host = env[HTTP_HOST]
|
||||||
if colon = host.index(":")
|
if colon = host.index(":")
|
||||||
|
@ -250,8 +277,19 @@ module Puma
|
||||||
env[REMOTE_ADDR] = client.peeraddr.last
|
env[REMOTE_ADDR] = client.peeraddr.last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The object used for a request with no body. All requests with
|
||||||
|
# no body share this one object since it has no state.
|
||||||
EmptyBody = NullIO.new
|
EmptyBody = NullIO.new
|
||||||
|
|
||||||
|
# Given the request +env+ from +client+ and a partial request body
|
||||||
|
# in +body+, finish reading the body if there is one and invoke
|
||||||
|
# the rack app. Then construct the response and write it back to
|
||||||
|
# +client+
|
||||||
|
#
|
||||||
|
# +cl+ is the previously fetched Content-Length header if there
|
||||||
|
# was one. This is an optimization to keep from having to look
|
||||||
|
# it up again.
|
||||||
|
#
|
||||||
def handle_request(env, client, body, cl)
|
def handle_request(env, client, body, cl)
|
||||||
normalize_env env, client
|
normalize_env env, client
|
||||||
|
|
||||||
|
@ -265,6 +303,9 @@ module Puma
|
||||||
env[RACK_INPUT] = body
|
env[RACK_INPUT] = body
|
||||||
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
||||||
|
|
||||||
|
# A rack extension. If the app writes #call'ables to this
|
||||||
|
# array, we will invoke them when the request is done.
|
||||||
|
#
|
||||||
after_reply = env[RACK_AFTER_REPLY] = []
|
after_reply = env[RACK_AFTER_REPLY] = []
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
@ -287,6 +328,10 @@ module Puma
|
||||||
keep_alive = env[HTTP_CONNECTION] != CLOSE
|
keep_alive = env[HTTP_CONNECTION] != CLOSE
|
||||||
include_keepalive_header = false
|
include_keepalive_header = false
|
||||||
|
|
||||||
|
# An optimization. The most common response is 200, so we can
|
||||||
|
# reply with the proper 200 status without having to compute
|
||||||
|
# the response header.
|
||||||
|
#
|
||||||
if status == 200
|
if status == 200
|
||||||
client.write HTTP_11_200
|
client.write HTTP_11_200
|
||||||
else
|
else
|
||||||
|
@ -301,6 +346,8 @@ module Puma
|
||||||
keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
|
keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
|
||||||
include_keepalive_header = keep_alive
|
include_keepalive_header = keep_alive
|
||||||
|
|
||||||
|
# Same optimization as above for HTTP/1.1
|
||||||
|
#
|
||||||
if status == 200
|
if status == 200
|
||||||
client.write HTTP_10_200
|
client.write HTTP_10_200
|
||||||
else
|
else
|
||||||
|
@ -381,6 +428,13 @@ module Puma
|
||||||
return keep_alive
|
return keep_alive
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Given the requset +env+ from +client+ and the partial body +body+
|
||||||
|
# plus a potential Content-Length value +cl+, finish reading
|
||||||
|
# the body and return it.
|
||||||
|
#
|
||||||
|
# If the body is larger than MAX_BODY, a Tempfile object is used
|
||||||
|
# for the body, otherwise a StringIO is used.
|
||||||
|
#
|
||||||
def read_body(env, client, body, cl)
|
def read_body(env, client, body, cl)
|
||||||
content_length = cl.to_i
|
content_length = cl.to_i
|
||||||
|
|
||||||
|
@ -426,23 +480,32 @@ module Puma
|
||||||
return stream
|
return stream
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A fallback rack response if +@app+ raises as exception.
|
||||||
|
#
|
||||||
def lowlevel_error(e)
|
def lowlevel_error(e)
|
||||||
[500, {}, ["No application configured"]]
|
[500, {}, ["No application configured"]]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wait for all outstanding requests to finish.
|
# Wait for all outstanding requests to finish.
|
||||||
|
#
|
||||||
def graceful_shutdown
|
def graceful_shutdown
|
||||||
@thread_pool.shutdown if @thread_pool
|
@thread_pool.shutdown if @thread_pool
|
||||||
end
|
end
|
||||||
|
|
||||||
# Stops the acceptor thread and then causes the worker threads to finish
|
# Stops the acceptor thread and then causes the worker threads to finish
|
||||||
# off the request queue before finally exiting.
|
# off the request queue before finally exiting.
|
||||||
|
#
|
||||||
def stop(sync=false)
|
def stop(sync=false)
|
||||||
@notify << STOP_COMMAND
|
@notify << STOP_COMMAND
|
||||||
|
|
||||||
@thread.join if @thread && sync
|
@thread.join if @thread && sync
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# If the dnssd gem is installed, advertise any TCPServer's configured
|
||||||
|
# out via bonjour.
|
||||||
|
#
|
||||||
|
# +name+ is the host name to use when advertised.
|
||||||
|
#
|
||||||
def attempt_bonjour(name)
|
def attempt_bonjour(name)
|
||||||
begin
|
begin
|
||||||
require 'dnssd'
|
require 'dnssd'
|
||||||
|
@ -468,6 +531,8 @@ module Puma
|
||||||
return announced
|
return announced
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Indicate if attempt_bonjour worked.
|
||||||
|
#
|
||||||
def bonjour_registered?
|
def bonjour_registered?
|
||||||
@bonjour_registered ||= false
|
@bonjour_registered ||= false
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
require 'thread'
|
require 'thread'
|
||||||
|
|
||||||
module Puma
|
module Puma
|
||||||
|
# A simple thread pool management object.
|
||||||
|
#
|
||||||
class ThreadPool
|
class ThreadPool
|
||||||
|
|
||||||
|
# Maintain a minimum of +min+ and maximum of +max+ threads
|
||||||
|
# in the pool.
|
||||||
|
#
|
||||||
|
# The block passed is the work that will be performed in each
|
||||||
|
# thread.
|
||||||
|
#
|
||||||
def initialize(min, max, &blk)
|
def initialize(min, max, &blk)
|
||||||
@todo = Queue.new
|
@todo = Queue.new
|
||||||
@mutex = Mutex.new
|
@mutex = Mutex.new
|
||||||
|
@ -20,6 +29,8 @@ module Puma
|
||||||
|
|
||||||
attr_reader :spawned
|
attr_reader :spawned
|
||||||
|
|
||||||
|
# How many objects have yet to be processed by the pool?
|
||||||
|
#
|
||||||
def backlog
|
def backlog
|
||||||
@todo.size
|
@todo.size
|
||||||
end
|
end
|
||||||
|
@ -27,6 +38,7 @@ module Puma
|
||||||
Stop = Object.new
|
Stop = Object.new
|
||||||
Trim = Object.new
|
Trim = Object.new
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
def spawn_thread
|
def spawn_thread
|
||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
@spawned += 1
|
@spawned += 1
|
||||||
|
@ -64,6 +76,7 @@ module Puma
|
||||||
th
|
th
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add +work+ to the todo list for a Thread to pickup and process.
|
||||||
def <<(work)
|
def <<(work)
|
||||||
if @todo.num_waiting == 0 and @spawned < @max
|
if @todo.num_waiting == 0 and @spawned < @max
|
||||||
spawn_thread
|
spawn_thread
|
||||||
|
@ -72,6 +85,9 @@ module Puma
|
||||||
@todo << work
|
@todo << work
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# If too many threads are in the pool, tell one to finish go ahead
|
||||||
|
# and exit.
|
||||||
|
#
|
||||||
def trim
|
def trim
|
||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
if @spawned - @trim_requested > @min
|
if @spawned - @trim_requested > @min
|
||||||
|
@ -81,6 +97,8 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tell all threads in the pool to exit and wait for them to finish.
|
||||||
|
#
|
||||||
def shutdown
|
def shutdown
|
||||||
@spawned.times do
|
@spawned.times do
|
||||||
@todo << Stop
|
@todo << Stop
|
||||||
|
|
Loading…
Reference in a new issue