Transfer-Encoding: chunked streaming input support
[unicorn.git] / lib / unicorn / http_request.rb
blob025b1255825f2f5fb6c51f3879b205c7994208c3
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       # some applications (like Echo) may want to change this to true
21       # We disable streaming by default since some (arguably broken)
22       # applications may not ever read the entire body and be confused
23       # when it receives a response after nothing has been sent to it.
24       Const::STREAM_INPUT => false,
25       # this is not in the Rack spec, but some apps may rely on it
26       "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze
27     }
29     Z = ''
30     Z.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding)
31     NULL_IO = StringIO.new(Z)
32     TEE = TeeInput.new
33     DECHUNKER = ChunkedReader.new
34     LOCALHOST = '127.0.0.1'.freeze
36     # Being explicitly single-threaded, we have certain advantages in
37     # not having to worry about variables being clobbered :)
38     BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
39     PARSER = HttpParser.new
40     PARAMS = Hash.new
42     def initialize(logger = Configurator::DEFAULT_LOGGER)
43       @logger = logger
44     end
46     # Does the majority of the IO processing.  It has been written in
47     # Ruby using about 8 different IO processing strategies.
48     #
49     # It is currently carefully constructed to make sure that it gets
50     # the best possible performance for the common case: GET requests
51     # that are fully complete after a single read(2)
52     #
53     # Anyone who thinks they can make it faster is more than welcome to
54     # take a crack at it.
55     #
56     # returns an environment hash suitable for Rack if successful
57     # This does minimal exception trapping and it is up to the caller
58     # to handle any socket errors (e.g. user aborted upload).
59     def read(socket)
60       PARAMS.clear
61       PARSER.reset
63       # From http://www.ietf.org/rfc/rfc3875:
64       # "Script authors should be aware that the REMOTE_ADDR and
65       #  REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
66       #  may not identify the ultimate source of the request.  They
67       #  identify the client for the immediate request to the server;
68       #  that client may be a proxy, gateway, or other intermediary
69       #  acting on behalf of the actual source client."
70       PARAMS[Const::REMOTE_ADDR] =
71                     TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
73       # short circuit the common case with small GET requests first
74       PARSER.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and
75           return handle_body(socket)
77       data = BUFFER.dup # socket.readpartial will clobber BUFFER
79       # Parser is not done, queue up more data to read and continue parsing
80       # an Exception thrown from the PARSER will throw us out of the loop
81       begin
82         data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
83         PARSER.execute(PARAMS, data) and return handle_body(socket)
84       end while true
85       rescue HttpParserError => e
86         @logger.error "HTTP parse error, malformed request " \
87                       "(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] ||
88                           PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}"
89         @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
90                       "PARAMS: #{PARAMS.inspect}\n---\n"
91         raise e
92     end
94     private
96     # Handles dealing with the rest of the request
97     # returns a Rack environment if successful
98     def handle_body(socket)
99       http_body = PARAMS.delete(:http_body)
101       length = PARAMS[Const::CONTENT_LENGTH].to_i
102       if te = PARAMS[Const::HTTP_TRANSFER_ENCODING]
103         if /chunked/i =~ te
104           socket = DECHUNKER.reopen(socket, http_body)
105           length = http_body = nil
106         end
107       end
109       inp = TEE.reopen(socket, length, http_body)
110       PARAMS[Const::RACK_INPUT] =
111                           DEFAULTS[Const::STREAM_INPUT] ? inp : inp.consume
112       PARAMS.update(DEFAULTS)
113     end
115   end