handle_request

in lib/puma/server.rb [636:890]


    def handle_request(req, lines)
      env = req.env
      client = req.io

      return false if closed_socket?(client)

      normalize_env env, req

      env[PUMA_SOCKET] = client

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

      env[HIJACK_P] = true
      env[HIJACK] = req

      body = req.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 client, "HTTP/1.1 103 Early Hints\r\n".freeze

            headers.each_pair do |k, vs|
              if vs.respond_to?(:to_s) && !vs.to_s.empty?
                vs.to_s.split(NEWLINE).each do |v|
                  next if possible_header_injection?(v)
                  fast_write client, "#{k}: #{v}\r\n"
                end
              else
                fast_write client, "#{k}: #{vs}\r\n"
              end
            end

            fast_write client, "\r\n".freeze
          rescue ConnectionError
            
          end
        }
      end

      
      
      
      
      
      

      to_delete = nil
      to_add = nil

      env.each do |k,v|
        if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
          if to_delete
            to_delete << k
          else
            to_delete = [k]
          end

          unless to_add
            to_add = {}
          end

          to_add[k.gsub(",", "_")] = v
        end
      end

      if to_delete
        to_delete.each { |k| env.delete(k) }
        env.merge! to_add
      end

      
      
      
      after_reply = env[RACK_AFTER_REPLY] = []

      begin
        begin
          status, headers, res_body = @app.call(env)

          return :async if req.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.log "Detected force shutdown of a thread, returning 503"
          @events.unknown_error self, e, "Rack app"

          status = 503
          headers = {}
          res_body = ["Request was internally terminated early\n"]

        rescue Exception => e
          @events.unknown_error self, e, "Rack app", env

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

        content_length = nil
        no_body = head

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

        cork_socket client

        line_ending = LINE_END
        colon = COLON

        http_11 = if env[HTTP_VERSION] == HTTP_11
          allow_chunked = true
          keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
          include_keepalive_header = false

          
          
          
          
          if status == 200
            lines << HTTP_11_200
          else
            lines.append "HTTP/1.1 ", status.to_s, " ",
                         fetch_status_code(status), line_ending

            no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
          end
          true
        else
          allow_chunked = false
          keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
          include_keepalive_header = keep_alive

          
          
          if status == 200
            lines << HTTP_10_200
          else
            lines.append "HTTP/1.0 ", status.to_s, " ",
                         fetch_status_code(status), line_ending

            no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
          end
          false
        end

        response_hijack = nil

        headers.each do |k, vs|
          case k.downcase
          when CONTENT_LENGTH2
            next if possible_header_injection?(vs)
            content_length = vs
            next
          when TRANSFER_ENCODING
            allow_chunked = false
            content_length = nil
          when HIJACK
            response_hijack = vs
            next
          end

          if vs.respond_to?(:to_s) && !vs.to_s.empty?
            vs.to_s.split(NEWLINE).each do |v|
              next if possible_header_injection?(v)
              lines.append k, colon, v, line_ending
            end
          else
            lines.append k, colon, line_ending
          end
        end

        if include_keepalive_header
          lines << CONNECTION_KEEP_ALIVE
        elsif http_11 && !keep_alive
          lines << CONNECTION_CLOSE
        end

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

          lines << line_ending
          fast_write client, lines.to_s
          return keep_alive
        end

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

        lines << line_ending

        fast_write client, lines.to_s

        if response_hijack
          response_hijack.call client
          return :async
        end

        begin
          res_body.each do |part|
            next if part.bytesize.zero?
            if chunked
              fast_write client, part.bytesize.to_s(16)
              fast_write client, line_ending
              fast_write client, part
              fast_write client, line_ending
            else
              fast_write client, part
            end

            client.flush
          end

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

      ensure
        uncork_socket client

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

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

      return keep_alive
    end