Method: Puma::Request#handle_request

Defined in:
lib/puma/request.rb

#handle_request(client, lines, requests) ⇒ Boolean, :async

Takes the request contained in client, invokes the Rack application to construct the response and writes it back to client.io.

It’ll return false when the connection is closed, this doesn’t mean that the response wasn’t successful.

It’ll return :async if the connection remains open but will be handled elsewhere, i.e. the connection has been hijacked by the Rack application.

Finally, it’ll return true on keep-alive connections.

Parameters:

Returns:

  • (Boolean, :async)


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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/puma/request.rb', line 32

def handle_request(client, lines, requests)
  env = client.env
  io  = client.io   # io may be a MiniSSL::Socket

  return false if closed_socket?(io)

  normalize_env env, client

  env[PUMA_SOCKET] = io

  if env[HTTPS_KEY] && io.peercert
    env[PUMA_PEERCERT] = io.peercert
  end

  env[HIJACK_P] = true
  env[HIJACK] = client

  body = client.body

  head = env[REQUEST_METHOD] == HEAD

  env[RACK_INPUT] = body
  env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP

  if @early_hints
    env[EARLY_HINTS] = lambda { |headers|
      begin
        fast_write io, str_early_hints(headers)
      rescue ConnectionError => e
        @events.debug_error e
        # noop, if we lost the socket we just won't send the early hints
      end
    }
  end

  req_env_post_parse env

  # 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
    begin
      status, headers, res_body = @thread_pool.with_force_shutdown do
        @app.call(env)
      end

      return :async if client.hijacked

      status = status.to_i

      if status == -1
        unless headers.empty? and res_body == []
          raise "async response must have empty headers and body"
        end

        return :async
      end
    rescue ThreadPool::ForceShutdown => e
      @events.unknown_error e, client, "Rack app"
      @events.log "Detected force shutdown of a thread"

      status, headers, res_body = lowlevel_error(e, env, 503)
    rescue Exception => e
      @events.unknown_error e, client, "Rack app"

      status, headers, res_body = lowlevel_error(e, env, 500)
    end

    res_info = {}
    res_info[:content_length] = nil
    res_info[:no_body] = head

    res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1
      res_body[0].bytesize
    else
      nil
    end

    cork_socket io

    str_headers(env, status, headers, res_info, lines, requests, client)

    line_ending = LINE_END

    content_length  = res_info[:content_length]
    response_hijack = res_info[:response_hijack]

    if res_info[:no_body]
      if content_length and status != 204
        lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
      end

      lines << LINE_END
      fast_write io, lines.to_s
      return res_info[:keep_alive]
    end

    if content_length
      lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
      chunked = false
    elsif !response_hijack and res_info[:allow_chunked]
      lines << TRANSFER_ENCODING_CHUNKED
      chunked = true
    end

    lines << line_ending

    fast_write io, lines.to_s

    if response_hijack
      response_hijack.call io
      return :async
    end

    begin
      res_body.each do |part|
        next if part.bytesize.zero?
        if chunked
           fast_write io, (part.bytesize.to_s(16) << line_ending)
           fast_write io, part            # part may have different encoding
           fast_write io, line_ending
        else
          fast_write io, part
        end
        io.flush
      end

      if chunked
        fast_write io, CLOSE_CHUNKED
        io.flush
      end
    rescue SystemCallError, IOError
      raise ConnectionError, "Connection error detected during write"
    end

  ensure
    uncork_socket io

    body.close
    client.tempfile.unlink if client.tempfile
    res_body.close if res_body.respond_to? :close

    after_reply.each { |o| o.call }
  end

  res_info[:keep_alive]
end