1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
require 'thin_parser'
require 'tempfile'
module Thin
# Raised when an incoming request is not valid
# and the server can not process it.
class InvalidRequest < IOError; end
# A request sent by the client to the server.
class Request
# Maximum request body size before it is moved out of memory
# and into a tempfile for reading.
MAX_BODY = 1024 * (80 + 32)
BODY_TMPFILE = 'thin-body'.freeze
MAX_HEADER = 1024 * (80 + 32)
# Freeze some HTTP header names & values
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
SERVER_NAME = 'SERVER_NAME'.freeze
LOCALHOST = 'localhost'.freeze
HTTP_VERSION = 'HTTP_VERSION'.freeze
HTTP_1_0 = 'HTTP/1.0'.freeze
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
CONNECTION = 'HTTP_CONNECTION'.freeze
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
CLOSE_REGEXP = /\bclose\b/i.freeze
# Freeze some Rack header names
RACK_INPUT = 'rack.input'.freeze
RACK_VERSION = 'rack.version'.freeze
RACK_ERRORS = 'rack.errors'.freeze
RACK_MULTITHREAD = 'rack.multithread'.freeze
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
RACK_RUN_ONCE = 'rack.run_once'.freeze
ASYNC_CALLBACK = 'async.callback'.freeze
ASYNC_CLOSE = 'async.close'.freeze
# CGI-like request environment variables
attr_reader :env
# Unparsed data of the request
attr_reader :data
# Request body
attr_reader :body
def initialize
@parser = Thin::HttpParser.new
@data = ''
@nparsed = 0
@body = StringIO.new
@env = {
SERVER_SOFTWARE => SERVER,
SERVER_NAME => LOCALHOST,
# Rack stuff
RACK_INPUT => @body,
RACK_VERSION => VERSION::RACK,
RACK_ERRORS => STDERR,
RACK_MULTITHREAD => false,
RACK_MULTIPROCESS => false,
RACK_RUN_ONCE => false
}
end
# Parse a chunk of data into the request environment
# Raises a +InvalidRequest+ if invalid.
# Returns +true+ if the parsing is complete.
def parse(data)
if @parser.finished? # Header finished, can only be some more body
body << data
else # Parse more header using the super parser
@data << data
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
@nparsed = @parser.execute(@env, @data, @nparsed)
# Transfert to a tempfile if body is very big
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
end
if finished? # Check if header and body are complete
@data = nil
@body.rewind
true # Request is fully parsed
else
false # Not finished, need more data
end
end
# +true+ if headers and body are finished parsing
def finished?
@parser.finished? && @body.size >= content_length
end
# Expected size of the body
def content_length
@env[CONTENT_LENGTH].to_i
end
# Returns +true+ if the client expect the connection to be persistent.
def persistent?
# Clients and servers SHOULD NOT assume that a persistent connection
# is maintained for HTTP versions less than 1.1 unless it is explicitly
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
if @env[HTTP_VERSION] == HTTP_1_0
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
# HTTP/1.1 client intends to maintain a persistent connection unless
# a Connection header including the connection-token "close" was sent
# in the request
else
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
end
end
def remote_address=(address)
@env[REMOTE_ADDR] = address
end
def threaded=(value)
@env[RACK_MULTITHREAD] = value
end
def async_callback=(callback)
@env[ASYNC_CALLBACK] = callback
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
end
def async_close
@async_close ||= @env[ASYNC_CLOSE]
end
# Close any resource used by the request
def close
@body.delete if @body.class == Tempfile
end
private
def move_body_to_tempfile
current_body = @body
current_body.rewind
@body = Tempfile.new(BODY_TMPFILE)
@body.binmode
@body << current_body.read
@env[RACK_INPUT] = @body
end
end
end
|