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'
|
||||
|
||||
module Puma
|
||||
# Handles invoke a Puma::Server in a command line style.
|
||||
#
|
||||
class CLI
|
||||
DefaultTCPHost = "0.0.0.0"
|
||||
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)
|
||||
@argv = argv
|
||||
@stdout = stdout
|
||||
|
@ -21,15 +29,21 @@ module Puma
|
|||
setup_options
|
||||
end
|
||||
|
||||
# Write +str+ to +@stdout+
|
||||
#
|
||||
def log(str)
|
||||
@stdout.puts str
|
||||
end
|
||||
|
||||
# Write +str+ to +@stderr+
|
||||
#
|
||||
def error(str)
|
||||
@stderr.puts "ERROR: #{str}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Build the OptionParser object to handle the available options.
|
||||
#
|
||||
def setup_options
|
||||
@options = {
|
||||
:min_threads => 0,
|
||||
|
@ -73,6 +87,9 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
# Load the specified rackup file, pull an options from
|
||||
# the rackup file, and set @app.
|
||||
#
|
||||
def load_rackup
|
||||
@app, options = Rack::Builder.parse_file @rackup
|
||||
@options.merge! options
|
||||
|
@ -84,6 +101,9 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
# If configured, write the pid of the current process out
|
||||
# to a file.
|
||||
#
|
||||
def write_pid
|
||||
if path = @options[:pidfile]
|
||||
File.open(path, "w") do |f|
|
||||
|
@ -92,10 +112,14 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def parse_options
|
||||
@parser.parse! @argv
|
||||
end
|
||||
|
||||
# Parse the options, load the rackup, start the server and wait
|
||||
# for it to finish.
|
||||
#
|
||||
def run
|
||||
parse_options
|
||||
|
||||
|
|
|
@ -2,10 +2,17 @@ require 'puma/const'
|
|||
require 'stringio'
|
||||
|
||||
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
|
||||
|
||||
include Const
|
||||
|
||||
# Create an Events object that prints to +stdout+ and +stderr+.
|
||||
#
|
||||
def initialize(stdout, stderr)
|
||||
@stdout = stdout
|
||||
@stderr = stderr
|
||||
|
@ -13,11 +20,19 @@ module Puma
|
|||
|
||||
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)
|
||||
@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"
|
||||
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")
|
||||
if error.respond_to? :render
|
||||
error.render "#{Time.now}: #{kind} error", @stderr
|
||||
|
@ -29,6 +44,9 @@ module Puma
|
|||
|
||||
DEFAULT = new(STDOUT, STDERR)
|
||||
|
||||
# Returns an Events object which writes it's status to 2 StringIO
|
||||
# objects.
|
||||
#
|
||||
def self.strings
|
||||
Events.new StringIO.new, StringIO.new
|
||||
end
|
||||
|
|
|
@ -1,19 +1,34 @@
|
|||
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
|
||||
|
||||
# Always returns nil
|
||||
#
|
||||
def gets
|
||||
nil
|
||||
end
|
||||
|
||||
# Never yields
|
||||
#
|
||||
def each
|
||||
end
|
||||
|
||||
# Always returns nil
|
||||
#
|
||||
def read(count)
|
||||
nil
|
||||
end
|
||||
|
||||
# Does nothing
|
||||
#
|
||||
def rewind
|
||||
end
|
||||
|
||||
# Does nothing
|
||||
#
|
||||
def close
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
require 'rack/commonlogger'
|
||||
|
||||
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
|
||||
remove_method :call
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ require 'puma_http11'
|
|||
require 'socket'
|
||||
|
||||
module Puma
|
||||
|
||||
# The HTTP Server itself. Serves out a single Rack app.
|
||||
class Server
|
||||
|
||||
include Puma::Const
|
||||
|
@ -24,11 +26,13 @@ module Puma
|
|||
attr_accessor :max_threads
|
||||
attr_accessor :persistent_timeout
|
||||
|
||||
# Creates a working server on host:port (strange things happen if port
|
||||
# isn't a Number).
|
||||
# Create a server for the rack app +app+.
|
||||
#
|
||||
# Use HttpServer#run to start the server and HttpServer#acceptor.join to
|
||||
# join the thread that's processing incoming requests on the socket.
|
||||
# +events+ is an object which will be called when certain error events occur
|
||||
# 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)
|
||||
@app = app
|
||||
|
@ -62,6 +66,9 @@ module Puma
|
|||
}
|
||||
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/
|
||||
# 6 == Socket::IPPROTO_TCP
|
||||
# 3 == TCP_CORK
|
||||
|
@ -81,6 +88,10 @@ module Puma
|
|||
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)
|
||||
s = TCPServer.new(host, port)
|
||||
if optimize_for_latency
|
||||
|
@ -89,13 +100,15 @@ module Puma
|
|||
@ios << s
|
||||
end
|
||||
|
||||
# Tell the server to listen on +path+ as a UNIX domain socket.
|
||||
#
|
||||
def add_unix_listener(path)
|
||||
@ios << UNIXServer.new(path)
|
||||
end
|
||||
|
||||
# Runs the server. It returns the thread used so you can "join" it.
|
||||
# You can also access the HttpServer#acceptor attribute to get the
|
||||
# thread later.
|
||||
# Runs the server. It returns the thread used so you can join it.
|
||||
# The thread is always available via #thread to be join'd
|
||||
#
|
||||
def run
|
||||
BasicSocket.do_not_reverse_lookup = true
|
||||
|
||||
|
@ -137,6 +150,7 @@ module Puma
|
|||
return @thread
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def handle_check
|
||||
cmd = @check.read(1)
|
||||
|
||||
|
@ -149,6 +163,12 @@ module Puma
|
|||
return false
|
||||
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)
|
||||
parser = HttpParser.new
|
||||
|
||||
|
@ -198,12 +218,16 @@ module Puma
|
|||
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
|
||||
@events.parse_error self, env, e
|
||||
|
||||
# Server error
|
||||
rescue StandardError => e
|
||||
@events.unknown_error self, env, e, "Read"
|
||||
|
||||
|
@ -218,6 +242,9 @@ module Puma
|
|||
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)
|
||||
if host = env[HTTP_HOST]
|
||||
if colon = host.index(":")
|
||||
|
@ -250,8 +277,19 @@ module Puma
|
|||
env[REMOTE_ADDR] = client.peeraddr.last
|
||||
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
|
||||
|
||||
# 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)
|
||||
normalize_env env, client
|
||||
|
||||
|
@ -265,6 +303,9 @@ module Puma
|
|||
env[RACK_INPUT] = body
|
||||
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] = []
|
||||
|
||||
begin
|
||||
|
@ -287,6 +328,10 @@ module Puma
|
|||
keep_alive = env[HTTP_CONNECTION] != CLOSE
|
||||
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
|
||||
client.write HTTP_11_200
|
||||
else
|
||||
|
@ -301,6 +346,8 @@ module Puma
|
|||
keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
|
||||
include_keepalive_header = keep_alive
|
||||
|
||||
# Same optimization as above for HTTP/1.1
|
||||
#
|
||||
if status == 200
|
||||
client.write HTTP_10_200
|
||||
else
|
||||
|
@ -381,6 +428,13 @@ module Puma
|
|||
return keep_alive
|
||||
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)
|
||||
content_length = cl.to_i
|
||||
|
||||
|
@ -426,23 +480,32 @@ module Puma
|
|||
return stream
|
||||
end
|
||||
|
||||
# A fallback rack response if +@app+ raises as exception.
|
||||
#
|
||||
def lowlevel_error(e)
|
||||
[500, {}, ["No application configured"]]
|
||||
end
|
||||
|
||||
# Wait for all outstanding requests to finish.
|
||||
#
|
||||
def graceful_shutdown
|
||||
@thread_pool.shutdown if @thread_pool
|
||||
end
|
||||
|
||||
# Stops the acceptor thread and then causes the worker threads to finish
|
||||
# off the request queue before finally exiting.
|
||||
#
|
||||
def stop(sync=false)
|
||||
@notify << STOP_COMMAND
|
||||
|
||||
@thread.join if @thread && sync
|
||||
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)
|
||||
begin
|
||||
require 'dnssd'
|
||||
|
@ -468,6 +531,8 @@ module Puma
|
|||
return announced
|
||||
end
|
||||
|
||||
# Indicate if attempt_bonjour worked.
|
||||
#
|
||||
def bonjour_registered?
|
||||
@bonjour_registered ||= false
|
||||
end
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
require 'thread'
|
||||
|
||||
module Puma
|
||||
# A simple thread pool management object.
|
||||
#
|
||||
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)
|
||||
@todo = Queue.new
|
||||
@mutex = Mutex.new
|
||||
|
@ -20,6 +29,8 @@ module Puma
|
|||
|
||||
attr_reader :spawned
|
||||
|
||||
# How many objects have yet to be processed by the pool?
|
||||
#
|
||||
def backlog
|
||||
@todo.size
|
||||
end
|
||||
|
@ -27,6 +38,7 @@ module Puma
|
|||
Stop = Object.new
|
||||
Trim = Object.new
|
||||
|
||||
# :nodoc:
|
||||
def spawn_thread
|
||||
@mutex.synchronize do
|
||||
@spawned += 1
|
||||
|
@ -64,6 +76,7 @@ module Puma
|
|||
th
|
||||
end
|
||||
|
||||
# Add +work+ to the todo list for a Thread to pickup and process.
|
||||
def <<(work)
|
||||
if @todo.num_waiting == 0 and @spawned < @max
|
||||
spawn_thread
|
||||
|
@ -72,6 +85,9 @@ module Puma
|
|||
@todo << work
|
||||
end
|
||||
|
||||
# If too many threads are in the pool, tell one to finish go ahead
|
||||
# and exit.
|
||||
#
|
||||
def trim
|
||||
@mutex.synchronize do
|
||||
if @spawned - @trim_requested > @min
|
||||
|
@ -81,6 +97,8 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
# Tell all threads in the pool to exit and wait for them to finish.
|
||||
#
|
||||
def shutdown
|
||||
@spawned.times do
|
||||
@todo << Stop
|
||||
|
|
Loading…
Reference in a new issue