chunked_reader: simpler interface
[unicorn.git] / lib / unicorn / http_request.rb
blobb2c72f367afa7f97998b6ba6bceb2b058e75499a
1 require 'stringio'
3 # compiled extension
4 require 'unicorn/http11'
6 module Unicorn
7   class HttpRequest
9     attr_accessor :logger
11     # default parameters we merge into the request env for Rack handlers
12     DEFAULTS = {
13       "rack.errors" => $stderr,
14       "rack.multiprocess" => true,
15       "rack.multithread" => false,
16       "rack.run_once" => false,
17       "rack.version" => [1, 0].freeze,
18       "SCRIPT_NAME" => "".freeze,
20       # this is not in the Rack spec, but some apps may rely on it
21       "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze
22     }
24     NULL_IO = StringIO.new(Z)
25     LOCALHOST = '127.0.0.1'.freeze
27     # Being explicitly single-threaded, we have certain advantages in
28     # not having to worry about variables being clobbered :)
29     BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
30     PARSER = HttpParser.new
31     PARAMS = Hash.new
33     def initialize(logger = Configurator::DEFAULT_LOGGER)
34       @logger = logger
35     end
37     # Does the majority of the IO processing.  It has been written in
38     # Ruby using about 8 different IO processing strategies.
39     #
40     # It is currently carefully constructed to make sure that it gets
41     # the best possible performance for the common case: GET requests
42     # that are fully complete after a single read(2)
43     #
44     # Anyone who thinks they can make it faster is more than welcome to
45     # take a crack at it.
46     #
47     # returns an environment hash suitable for Rack if successful
48     # This does minimal exception trapping and it is up to the caller
49     # to handle any socket errors (e.g. user aborted upload).
50     def read(socket)
51       PARAMS.clear
52       PARSER.reset
54       # From http://www.ietf.org/rfc/rfc3875:
55       # "Script authors should be aware that the REMOTE_ADDR and
56       #  REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
57       #  may not identify the ultimate source of the request.  They
58       #  identify the client for the immediate request to the server;
59       #  that client may be a proxy, gateway, or other intermediary
60       #  acting on behalf of the actual source client."
61       PARAMS[Const::REMOTE_ADDR] =
62                     TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
64       # short circuit the common case with small GET requests first
65       PARSER.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and
66           return handle_body(socket)
68       data = BUFFER.dup # socket.readpartial will clobber BUFFER
70       # Parser is not done, queue up more data to read and continue parsing
71       # an Exception thrown from the PARSER will throw us out of the loop
72       begin
73         data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
74         PARSER.execute(PARAMS, data) and return handle_body(socket)
75       end while true
76       rescue HttpParserError => e
77         @logger.error "HTTP parse error, malformed request " \
78                       "(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] ||
79                           PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}"
80         @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
81                       "PARAMS: #{PARAMS.inspect}\n---\n"
82         raise e
83     end
85     private
87     # Handles dealing with the rest of the request
88     # returns a Rack environment if successful
89     def handle_body(socket)
90       PARAMS[Const::RACK_INPUT] = if (body = PARAMS.delete(:http_body))
91         length = PARAMS[Const::CONTENT_LENGTH].to_i
93         if te = PARAMS[Const::HTTP_TRANSFER_ENCODING]
94           if /chunked/i =~ te
95             socket = ChunkedReader.new(socket, body)
96             length = body = nil
97           end
98         end
100         inp = TeeInput.new(socket, length, body)
101         DEFAULTS[Const::STREAM_INPUT] ? inp : inp.consume
102       else
103         NULL_IO.closed? ? NULL_IO.reopen(Z) : NULL_IO
104       end
106       PARAMS.update(DEFAULTS)
107     end
109   end