puma 5.6.4-java → 6.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +136 -3
- data/README.md +21 -17
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +18 -10
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +63 -24
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +6 -3
- data/lib/puma/binder.rb +37 -43
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +22 -12
- data/lib/puma/cluster/worker.rb +13 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +28 -25
- data/lib/puma/configuration.rb +74 -58
- data/lib/puma/const.rb +14 -18
- data/lib/puma/control_cli.rb +21 -18
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +94 -49
- data/lib/puma/error_logger.rb +17 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +29 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +107 -156
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +91 -15
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +3 -3
- data/lib/puma/request.rb +291 -156
- data/lib/puma/runner.rb +41 -20
- data/lib/puma/server.rb +53 -64
- data/lib/puma/single.rb +10 -10
- data/lib/puma/state_file.rb +2 -4
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +16 -13
- data/lib/puma/util.rb +12 -14
- data/lib/puma.rb +11 -8
- data/lib/rack/handler/puma.rb +9 -9
- metadata +7 -3
- data/lib/puma/queue_close.rb +0 -26
    
        data/lib/puma/request.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Puma
         | 
| 4 | 
            +
              #———————————————————————— DO NOT USE — this class is for internal use only ———
         | 
| 5 | 
            +
             | 
| 4 6 |  | 
| 5 7 | 
             
              # The methods here are included in Server, but are separated into this file.
         | 
| 6 8 | 
             
              # All the methods here pertain to passing the request to the app, then
         | 
| @@ -10,7 +12,21 @@ module Puma | |
| 10 12 | 
             
              # #handle_request, which is called in Server#process_client.
         | 
| 11 13 | 
             
              # @version 5.0.3
         | 
| 12 14 | 
             
              #
         | 
| 13 | 
            -
              module Request
         | 
| 15 | 
            +
              module Request # :nodoc:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                # determines whether to write body to io_buffer first, or straight to socket
         | 
| 18 | 
            +
                # also fixes max size of chunked body read when bosy is an IO.
         | 
| 19 | 
            +
                BODY_LEN_MAX   = 1_024 * 256
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # size divide for using copy_stream on body
         | 
| 22 | 
            +
                IO_BODY_MAX = 1_024 * 64
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # max size for io_buffer, force write when exceeded
         | 
| 25 | 
            +
                IO_BUFFER_LEN_MAX = 1_024 * 1_024 * 4
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                CUSTOM_STAT = 'CUSTOM'
         | 
| 14 30 |  | 
| 15 31 | 
             
                include Puma::Const
         | 
| 16 32 |  | 
| @@ -25,40 +41,36 @@ module Puma | |
| 25 41 | 
             
                #
         | 
| 26 42 | 
             
                # Finally, it'll return +true+ on keep-alive connections.
         | 
| 27 43 | 
             
                # @param client [Puma::Client]
         | 
| 28 | 
            -
                # @param  | 
| 44 | 
            +
                # @param io_buffer [Puma::IOBuffer]
         | 
| 29 45 | 
             
                # @param requests [Integer]
         | 
| 30 46 | 
             
                # @return [Boolean,:async]
         | 
| 31 47 | 
             
                #
         | 
| 32 | 
            -
                def handle_request(client,  | 
| 48 | 
            +
                def handle_request(client, io_buffer, requests)
         | 
| 33 49 | 
             
                  env = client.env
         | 
| 34 | 
            -
                   | 
| 50 | 
            +
                  socket  = client.io   # io may be a MiniSSL::Socket
         | 
| 35 51 |  | 
| 36 | 
            -
                  return false if closed_socket?( | 
| 52 | 
            +
                  return false if closed_socket?(socket)
         | 
| 37 53 |  | 
| 38 54 | 
             
                  normalize_env env, client
         | 
| 39 55 |  | 
| 40 | 
            -
                  env[PUMA_SOCKET] =  | 
| 56 | 
            +
                  env[PUMA_SOCKET] = socket
         | 
| 41 57 |  | 
| 42 | 
            -
                  if env[HTTPS_KEY] &&  | 
| 43 | 
            -
                    env[PUMA_PEERCERT] =  | 
| 58 | 
            +
                  if env[HTTPS_KEY] && socket.peercert
         | 
| 59 | 
            +
                    env[PUMA_PEERCERT] = socket.peercert
         | 
| 44 60 | 
             
                  end
         | 
| 45 61 |  | 
| 46 62 | 
             
                  env[HIJACK_P] = true
         | 
| 47 63 | 
             
                  env[HIJACK] = client
         | 
| 48 64 |  | 
| 49 | 
            -
                   | 
| 50 | 
            -
             | 
| 51 | 
            -
                  head = env[REQUEST_METHOD] == HEAD
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                  env[RACK_INPUT] = body
         | 
| 65 | 
            +
                  env[RACK_INPUT] = client.body
         | 
| 54 66 | 
             
                  env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
         | 
| 55 67 |  | 
| 56 68 | 
             
                  if @early_hints
         | 
| 57 69 | 
             
                    env[EARLY_HINTS] = lambda { |headers|
         | 
| 58 70 | 
             
                      begin
         | 
| 59 | 
            -
                         | 
| 71 | 
            +
                        fast_write_str socket, str_early_hints(headers)
         | 
| 60 72 | 
             
                      rescue ConnectionError => e
         | 
| 61 | 
            -
                        @ | 
| 73 | 
            +
                        @log_writer.debug_error e
         | 
| 62 74 | 
             
                        # noop, if we lost the socket we just won't send the early hints
         | 
| 63 75 | 
             
                      end
         | 
| 64 76 | 
             
                    }
         | 
| @@ -69,119 +81,147 @@ module Puma | |
| 69 81 | 
             
                  # A rack extension. If the app writes #call'ables to this
         | 
| 70 82 | 
             
                  # array, we will invoke them when the request is done.
         | 
| 71 83 | 
             
                  #
         | 
| 72 | 
            -
                   | 
| 84 | 
            +
                  env[RACK_AFTER_REPLY] ||= []
         | 
| 73 85 |  | 
| 74 86 | 
             
                  begin
         | 
| 75 | 
            -
                     | 
| 87 | 
            +
                    if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
         | 
| 76 88 | 
             
                      status, headers, res_body = @thread_pool.with_force_shutdown do
         | 
| 77 89 | 
             
                        @app.call(env)
         | 
| 78 90 | 
             
                      end
         | 
| 91 | 
            +
                    else
         | 
| 92 | 
            +
                      @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
         | 
| 93 | 
            +
                      status, headers, res_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
         | 
| 94 | 
            +
                    end
         | 
| 79 95 |  | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
                      status = status.to_i
         | 
| 96 | 
            +
                    return :async if client.hijacked
         | 
| 83 97 |  | 
| 84 | 
            -
             | 
| 85 | 
            -
                        unless headers.empty? and res_body == []
         | 
| 86 | 
            -
                          raise "async response must have empty headers and body"
         | 
| 87 | 
            -
                        end
         | 
| 98 | 
            +
                    status = status.to_i
         | 
| 88 99 |  | 
| 89 | 
            -
             | 
| 100 | 
            +
                    if status == -1
         | 
| 101 | 
            +
                      unless headers.empty? and res_body == []
         | 
| 102 | 
            +
                        raise "async response must have empty headers and body"
         | 
| 90 103 | 
             
                      end
         | 
| 91 | 
            -
                    rescue ThreadPool::ForceShutdown => e
         | 
| 92 | 
            -
                      @events.unknown_error e, client, "Rack app"
         | 
| 93 | 
            -
                      @events.log "Detected force shutdown of a thread"
         | 
| 94 104 |  | 
| 95 | 
            -
                       | 
| 96 | 
            -
                    rescue Exception => e
         | 
| 97 | 
            -
                      @events.unknown_error e, client, "Rack app"
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                      status, headers, res_body = lowlevel_error(e, env, 500)
         | 
| 105 | 
            +
                      return :async
         | 
| 100 106 | 
             
                    end
         | 
| 107 | 
            +
                  rescue ThreadPool::ForceShutdown => e
         | 
| 108 | 
            +
                    @log_writer.unknown_error e, client, "Rack app"
         | 
| 109 | 
            +
                    @log_writer.log "Detected force shutdown of a thread"
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    status, headers, res_body = lowlevel_error(e, env, 503)
         | 
| 112 | 
            +
                  rescue Exception => e
         | 
| 113 | 
            +
                    @log_writer.unknown_error e, client, "Rack app"
         | 
| 101 114 |  | 
| 102 | 
            -
                     | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 115 | 
            +
                    status, headers, res_body = lowlevel_error(e, env, 500)
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                  prepare_response(status, headers, res_body, io_buffer, requests, client)
         | 
| 118 | 
            +
                end
         | 
| 105 119 |  | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 120 | 
            +
                # Assembles the headers and prepares the body for actually sending the
         | 
| 121 | 
            +
                # response via #fast_write_response.
         | 
| 122 | 
            +
                #
         | 
| 123 | 
            +
                # @param status [Integer] the status returned by the Rack application
         | 
| 124 | 
            +
                # @param headers [Hash] the headers returned by the Rack application
         | 
| 125 | 
            +
                # @param app_body [Array] the body returned by the Rack application or
         | 
| 126 | 
            +
                #   a call to `lowlevel_error`
         | 
| 127 | 
            +
                # @param io_buffer [Puma::IOBuffer] modified in place
         | 
| 128 | 
            +
                # @param requests [Integer] number of inline requests handled
         | 
| 129 | 
            +
                # @param client [Puma::Client]
         | 
| 130 | 
            +
                # @return [Boolean,:async]
         | 
| 131 | 
            +
                def prepare_response(status, headers, app_body, io_buffer, requests, client)
         | 
| 132 | 
            +
                  env = client.env
         | 
| 133 | 
            +
                  socket  = client.io
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  after_reply = env[RACK_AFTER_REPLY] || []
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  return false if closed_socket?(socket)
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  resp_info = str_headers(env, status, headers, app_body, io_buffer, requests, client)
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  # below converts app_body into body, dependent on app_body's characteristics, and
         | 
| 142 | 
            +
                  # resp_info[:content_length] will be set if it can be determined
         | 
| 143 | 
            +
                  if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
         | 
| 144 | 
            +
                    if app_body.respond_to?(:to_ary)
         | 
| 145 | 
            +
                      length = 0
         | 
| 146 | 
            +
                      if array_body = app_body.to_ary
         | 
| 147 | 
            +
                        body = array_body.map { |part| length += part.bytesize; part }
         | 
| 148 | 
            +
                      elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
         | 
| 149 | 
            +
                        length = app_body.size
         | 
| 150 | 
            +
                      elsif app_body.respond_to?(:each)
         | 
| 151 | 
            +
                        body = []
         | 
| 152 | 
            +
                        app_body.each { |part| length += part.bytesize; body << part }
         | 
| 153 | 
            +
                      end
         | 
| 154 | 
            +
                      resp_info[:content_length] = length
         | 
| 155 | 
            +
                    elsif app_body.is_a?(File) && app_body.respond_to?(:size)
         | 
| 156 | 
            +
                      resp_info[:content_length] = app_body.size
         | 
| 157 | 
            +
                      body = app_body
         | 
| 158 | 
            +
                    elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
         | 
| 159 | 
            +
                        File.readable?(fn = app_body.to_path)
         | 
| 160 | 
            +
                      body = File.open fn, 'rb'
         | 
| 161 | 
            +
                      resp_info[:content_length] = body.size
         | 
| 108 162 | 
             
                    else
         | 
| 109 | 
            -
                       | 
| 163 | 
            +
                      body = app_body
         | 
| 110 164 | 
             
                    end
         | 
| 165 | 
            +
                  elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
         | 
| 166 | 
            +
                      File.readable?(fn = app_body.to_path)
         | 
| 167 | 
            +
                    body = File.open fn, 'rb'
         | 
| 168 | 
            +
                    resp_info[:content_length] = body.size
         | 
| 169 | 
            +
                  else
         | 
| 170 | 
            +
                    body = app_body
         | 
| 171 | 
            +
                  end
         | 
| 111 172 |  | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
                    str_headers(env, status, headers, res_info, lines, requests, client)
         | 
| 115 | 
            -
             | 
| 116 | 
            -
                    line_ending = LINE_END
         | 
| 173 | 
            +
                  line_ending = LINE_END
         | 
| 117 174 |  | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 175 | 
            +
                  content_length = resp_info[:content_length]
         | 
| 176 | 
            +
                  keep_alive     = resp_info[:keep_alive]
         | 
| 120 177 |  | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 178 | 
            +
                  if app_body && !app_body.respond_to?(:each)
         | 
| 179 | 
            +
                    response_hijack = app_body
         | 
| 180 | 
            +
                  else
         | 
| 181 | 
            +
                    response_hijack = resp_info[:response_hijack]
         | 
| 182 | 
            +
                  end
         | 
| 125 183 |  | 
| 126 | 
            -
             | 
| 127 | 
            -
                      fast_write io, lines.to_s
         | 
| 128 | 
            -
                      return res_info[:keep_alive]
         | 
| 129 | 
            -
                    end
         | 
| 184 | 
            +
                  cork_socket socket
         | 
| 130 185 |  | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
                       | 
| 134 | 
            -
                    elsif !response_hijack and res_info[:allow_chunked]
         | 
| 135 | 
            -
                      lines << TRANSFER_ENCODING_CHUNKED
         | 
| 136 | 
            -
                      chunked = true
         | 
| 186 | 
            +
                  if resp_info[:no_body]
         | 
| 187 | 
            +
                    if content_length and status != 204
         | 
| 188 | 
            +
                      io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
         | 
| 137 189 | 
             
                    end
         | 
| 138 190 |  | 
| 139 | 
            -
                     | 
| 140 | 
            -
             | 
| 141 | 
            -
                     | 
| 191 | 
            +
                    io_buffer << LINE_END
         | 
| 192 | 
            +
                    fast_write_str socket, io_buffer.to_s
         | 
| 193 | 
            +
                    return keep_alive
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
                  if content_length
         | 
| 196 | 
            +
                    io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
         | 
| 197 | 
            +
                    chunked = false
         | 
| 198 | 
            +
                  elsif !response_hijack and resp_info[:allow_chunked]
         | 
| 199 | 
            +
                    io_buffer << TRANSFER_ENCODING_CHUNKED
         | 
| 200 | 
            +
                    chunked = true
         | 
| 201 | 
            +
                  end
         | 
| 142 202 |  | 
| 143 | 
            -
             | 
| 144 | 
            -
                      response_hijack.call io
         | 
| 145 | 
            -
                      return :async
         | 
| 146 | 
            -
                    end
         | 
| 203 | 
            +
                  io_buffer << line_ending
         | 
| 147 204 |  | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
                           fast_write io, part            # part may have different encoding
         | 
| 154 | 
            -
                           fast_write io, line_ending
         | 
| 155 | 
            -
                        else
         | 
| 156 | 
            -
                          fast_write io, part
         | 
| 157 | 
            -
                        end
         | 
| 158 | 
            -
                        io.flush
         | 
| 159 | 
            -
                      end
         | 
| 205 | 
            +
                  if response_hijack
         | 
| 206 | 
            +
                    fast_write_str socket, io_buffer.to_s
         | 
| 207 | 
            +
                    response_hijack.call socket
         | 
| 208 | 
            +
                    return :async
         | 
| 209 | 
            +
                  end
         | 
| 160 210 |  | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
                  ensure
         | 
| 170 | 
            -
                    begin
         | 
| 171 | 
            -
                      uncork_socket io
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                      body.close
         | 
| 174 | 
            -
                      client.tempfile.unlink if client.tempfile
         | 
| 175 | 
            -
                    ensure
         | 
| 176 | 
            -
                      # Whatever happens, we MUST call `close` on the response body.
         | 
| 177 | 
            -
                      # Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
         | 
| 178 | 
            -
                      res_body.close if res_body.respond_to? :close
         | 
| 179 | 
            -
                    end
         | 
| 211 | 
            +
                  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
         | 
| 212 | 
            +
                  keep_alive
         | 
| 213 | 
            +
                ensure
         | 
| 214 | 
            +
                  io_buffer.reset
         | 
| 215 | 
            +
                  resp_info = nil
         | 
| 216 | 
            +
                  uncork_socket socket
         | 
| 217 | 
            +
                  app_body.close if app_body.respond_to? :close
         | 
| 218 | 
            +
                  client.tempfile&.unlink
         | 
| 180 219 |  | 
| 220 | 
            +
                  begin
         | 
| 181 221 | 
             
                    after_reply.each { |o| o.call }
         | 
| 182 | 
            -
                   | 
| 183 | 
            -
             | 
| 184 | 
            -
                   | 
| 222 | 
            +
                  rescue StandardError => e
         | 
| 223 | 
            +
                    @log_writer.debug_error e
         | 
| 224 | 
            +
                  end unless after_reply.empty?
         | 
| 185 225 | 
             
                end
         | 
| 186 226 |  | 
| 187 227 | 
             
                # @param env [Hash] see Puma::Client#env, from request
         | 
| @@ -195,45 +235,107 @@ module Puma | |
| 195 235 | 
             
                  end
         | 
| 196 236 | 
             
                end
         | 
| 197 237 |  | 
| 198 | 
            -
                #  | 
| 199 | 
            -
                #  | 
| 238 | 
            +
                # Used to write 'early hints', 'no body' responses, 'hijacked' responses,
         | 
| 239 | 
            +
                # and body segments (called by `fast_write_response`).
         | 
| 240 | 
            +
                # Writes a string to an io (normally `Client#io`) using `write_nonblock`.
         | 
| 241 | 
            +
                # Large strings may not be written in one pass, especially if `io` is a
         | 
| 242 | 
            +
                # `MiniSSL::Socket`.
         | 
| 243 | 
            +
                # @param io [#write_nonblock] the io to write to
         | 
| 200 244 | 
             
                # @param str [String] the string written to the io
         | 
| 201 245 | 
             
                # @raise [ConnectionError]
         | 
| 202 246 | 
             
                #
         | 
| 203 | 
            -
                def  | 
| 247 | 
            +
                def fast_write_str(socket, str)
         | 
| 204 248 | 
             
                  n = 0
         | 
| 205 | 
            -
                   | 
| 249 | 
            +
                  byte_size = str.bytesize
         | 
| 250 | 
            +
                  while n < byte_size
         | 
| 206 251 | 
             
                    begin
         | 
| 207 | 
            -
                      n  | 
| 252 | 
            +
                      n += socket.syswrite(n.zero? ? str : str.byteslice(n..-1))
         | 
| 208 253 | 
             
                    rescue Errno::EAGAIN, Errno::EWOULDBLOCK
         | 
| 209 | 
            -
                      unless  | 
| 210 | 
            -
                        raise ConnectionError,  | 
| 254 | 
            +
                      unless socket.wait_writable WRITE_TIMEOUT
         | 
| 255 | 
            +
                        raise ConnectionError, SOCKET_WRITE_ERR_MSG
         | 
| 211 256 | 
             
                      end
         | 
| 212 | 
            -
             | 
| 213 257 | 
             
                      retry
         | 
| 214 258 | 
             
                    rescue  Errno::EPIPE, SystemCallError, IOError
         | 
| 215 | 
            -
                      raise ConnectionError,  | 
| 259 | 
            +
                      raise ConnectionError, SOCKET_WRITE_ERR_MSG
         | 
| 216 260 | 
             
                    end
         | 
| 217 | 
            -
             | 
| 218 | 
            -
                    return if n == str.bytesize
         | 
| 219 | 
            -
                    str = str.byteslice(n..-1)
         | 
| 220 261 | 
             
                  end
         | 
| 221 262 | 
             
                end
         | 
| 222 | 
            -
                private :fast_write
         | 
| 223 263 |  | 
| 224 | 
            -
                #  | 
| 225 | 
            -
                #  | 
| 264 | 
            +
                # Used to write headers and body.
         | 
| 265 | 
            +
                # Writes to a socket (normally `Client#io`) using `#fast_write_str`.
         | 
| 266 | 
            +
                # Accumulates `body` items into `io_buffer`, then writes to socket.
         | 
| 267 | 
            +
                # @param socket [#write] the response socket
         | 
| 268 | 
            +
                # @param body [Enumerable, File] the body object
         | 
| 269 | 
            +
                # @param io_buffer [Puma::IOBuffer] contains headers
         | 
| 270 | 
            +
                # @param chunk [Boolean]
         | 
| 271 | 
            +
                # @raise [ConnectionError]
         | 
| 226 272 | 
             
                #
         | 
| 227 | 
            -
                def  | 
| 228 | 
            -
                   | 
| 273 | 
            +
                def fast_write_response(socket, body, io_buffer, chunked, content_length)
         | 
| 274 | 
            +
                  if body.is_a?(::File) || body.respond_to?(:read) || body.respond_to?(:readpartial)
         | 
| 275 | 
            +
                    if chunked  # would this ever happen?
         | 
| 276 | 
            +
                      while part = body.read(BODY_LEN_MAX)
         | 
| 277 | 
            +
                        io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
         | 
| 278 | 
            +
                      end
         | 
| 279 | 
            +
                      io_buffer << CLOSE_CHUNKED
         | 
| 280 | 
            +
                      fast_write_str socket, io_buffer.to_s
         | 
| 281 | 
            +
                    else
         | 
| 282 | 
            +
                      if content_length <= IO_BODY_MAX
         | 
| 283 | 
            +
                        io_buffer.write body.sysread(content_length)
         | 
| 284 | 
            +
                        fast_write_str socket, io_buffer.to_s
         | 
| 285 | 
            +
                      else
         | 
| 286 | 
            +
                        fast_write_str socket, io_buffer.to_s
         | 
| 287 | 
            +
                        IO.copy_stream body, socket
         | 
| 288 | 
            +
                      end
         | 
| 289 | 
            +
                    end
         | 
| 290 | 
            +
                    body.close
         | 
| 291 | 
            +
                  elsif body.is_a?(::Array) && body.length == 1
         | 
| 292 | 
            +
                    body_first = body.first
         | 
| 293 | 
            +
                    if body_first.is_a?(::String) && body_first.bytesize >= BODY_LEN_MAX
         | 
| 294 | 
            +
                      # large body, write both header & body to socket
         | 
| 295 | 
            +
                      fast_write_str socket, io_buffer.to_s
         | 
| 296 | 
            +
                      fast_write_str socket, body_first
         | 
| 297 | 
            +
                    else
         | 
| 298 | 
            +
                      # smaller body, write to stream first
         | 
| 299 | 
            +
                      io_buffer.write body_first
         | 
| 300 | 
            +
                      fast_write_str socket, io_buffer.to_s
         | 
| 301 | 
            +
                    end
         | 
| 302 | 
            +
                  else
         | 
| 303 | 
            +
                    # for array bodies, flush io_buffer to socket when size is greater than
         | 
| 304 | 
            +
                    # IO_BUFFER_LEN_MAX
         | 
| 305 | 
            +
                    if chunked
         | 
| 306 | 
            +
                      body.each do |part|
         | 
| 307 | 
            +
                        next if (byte_size = part.bytesize).zero?
         | 
| 308 | 
            +
                        io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
         | 
| 309 | 
            +
                        if io_buffer.length > IO_BUFFER_LEN_MAX
         | 
| 310 | 
            +
                          fast_write_str socket, io_buffer.to_s
         | 
| 311 | 
            +
                          io_buffer.reset
         | 
| 312 | 
            +
                        end
         | 
| 313 | 
            +
                      end
         | 
| 314 | 
            +
                      io_buffer.write CLOSE_CHUNKED
         | 
| 315 | 
            +
                    else
         | 
| 316 | 
            +
                      body.each do |part|
         | 
| 317 | 
            +
                        next if part.bytesize.zero?
         | 
| 318 | 
            +
                        io_buffer.write part
         | 
| 319 | 
            +
                        if io_buffer.length > IO_BUFFER_LEN_MAX
         | 
| 320 | 
            +
                          fast_write_str socket, io_buffer.to_s
         | 
| 321 | 
            +
                          io_buffer.reset
         | 
| 322 | 
            +
                        end
         | 
| 323 | 
            +
                      end
         | 
| 324 | 
            +
                    end
         | 
| 325 | 
            +
                    fast_write_str(socket, io_buffer.to_s) unless io_buffer.length.zero?
         | 
| 326 | 
            +
                  end
         | 
| 327 | 
            +
                rescue Errno::EAGAIN, Errno::EWOULDBLOCK
         | 
| 328 | 
            +
                  raise ConnectionError, SOCKET_WRITE_ERR_MSG
         | 
| 329 | 
            +
                rescue  Errno::EPIPE, SystemCallError, IOError
         | 
| 330 | 
            +
                  raise ConnectionError, SOCKET_WRITE_ERR_MSG
         | 
| 229 331 | 
             
                end
         | 
| 230 | 
            -
             | 
| 332 | 
            +
             | 
| 333 | 
            +
                private :fast_write_str, :fast_write_response
         | 
| 231 334 |  | 
| 232 335 | 
             
                # Given a Hash +env+ for the request read from +client+, add
         | 
| 233 336 | 
             
                # and fixup keys to comply with Rack's env guidelines.
         | 
| 234 337 | 
             
                # @param env [Hash] see Puma::Client#env, from request
         | 
| 235 338 | 
             
                # @param client [Puma::Client] only needed for Client#peerip
         | 
| 236 | 
            -
                # @todo make private in 6.0.0
         | 
| 237 339 | 
             
                #
         | 
| 238 340 | 
             
                def normalize_env(env, client)
         | 
| 239 341 | 
             
                  if host = env[HTTP_HOST]
         | 
| @@ -258,14 +360,12 @@ module Puma | |
| 258 360 | 
             
                    uri = URI.parse(env[REQUEST_URI])
         | 
| 259 361 | 
             
                    env[REQUEST_PATH] = uri.path
         | 
| 260 362 |  | 
| 261 | 
            -
                    raise "No REQUEST PATH" unless env[REQUEST_PATH]
         | 
| 262 | 
            -
             | 
| 263 363 | 
             
                    # A nil env value will cause a LintError (and fatal errors elsewhere),
         | 
| 264 364 | 
             
                    # so only set the env value if there actually is a value.
         | 
| 265 365 | 
             
                    env[QUERY_STRING] = uri.query if uri.query
         | 
| 266 366 | 
             
                  end
         | 
| 267 367 |  | 
| 268 | 
            -
                  env[PATH_INFO] = env[REQUEST_PATH]
         | 
| 368 | 
            +
                  env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
         | 
| 269 369 |  | 
| 270 370 | 
             
                  # From https://www.ietf.org/rfc/rfc3875 :
         | 
| 271 371 | 
             
                  # "Script authors should be aware that the REMOTE_ADDR and
         | 
| @@ -281,17 +381,31 @@ module Puma | |
| 281 381 | 
             
                      addr = client.peerip
         | 
| 282 382 | 
             
                    rescue Errno::ENOTCONN
         | 
| 283 383 | 
             
                      # Client disconnects can result in an inability to get the
         | 
| 284 | 
            -
                      # peeraddr from the socket; default to  | 
| 285 | 
            -
                       | 
| 384 | 
            +
                      # peeraddr from the socket; default to unspec.
         | 
| 385 | 
            +
                      if client.peer_family == Socket::AF_INET6
         | 
| 386 | 
            +
                        addr = UNSPECIFIED_IPV6
         | 
| 387 | 
            +
                      else
         | 
| 388 | 
            +
                        addr = UNSPECIFIED_IPV4
         | 
| 389 | 
            +
                      end
         | 
| 286 390 | 
             
                    end
         | 
| 287 391 |  | 
| 288 392 | 
             
                    # Set unix socket addrs to localhost
         | 
| 289 | 
            -
                     | 
| 393 | 
            +
                    if addr.empty?
         | 
| 394 | 
            +
                      if client.peer_family == Socket::AF_INET6
         | 
| 395 | 
            +
                        addr = LOCALHOST_IPV6
         | 
| 396 | 
            +
                      else
         | 
| 397 | 
            +
                        addr = LOCALHOST_IPV4
         | 
| 398 | 
            +
                      end
         | 
| 399 | 
            +
                    end
         | 
| 290 400 |  | 
| 291 401 | 
             
                    env[REMOTE_ADDR] = addr
         | 
| 292 402 | 
             
                  end
         | 
| 403 | 
            +
             | 
| 404 | 
            +
                  # The legacy HTTP_VERSION header can be sent as a client header.
         | 
| 405 | 
            +
                  # Rack v4 may remove using HTTP_VERSION.  If so, remove this line.
         | 
| 406 | 
            +
                  env[HTTP_VERSION] = env[SERVER_PROTOCOL]
         | 
| 293 407 | 
             
                end
         | 
| 294 | 
            -
                 | 
| 408 | 
            +
                private :normalize_env
         | 
| 295 409 |  | 
| 296 410 | 
             
                # @param header_key [#to_s]
         | 
| 297 411 | 
             
                # @return [Boolean]
         | 
| @@ -350,7 +464,7 @@ module Puma | |
| 350 464 | 
             
                # @version 5.0.3
         | 
| 351 465 | 
             
                #
         | 
| 352 466 | 
             
                def str_early_hints(headers)
         | 
| 353 | 
            -
                  eh_str = "HTTP/1.1 103 Early Hints\r\n" | 
| 467 | 
            +
                  eh_str = +"HTTP/1.1 103 Early Hints\r\n"
         | 
| 354 468 | 
             
                  headers.each_pair do |k, vs|
         | 
| 355 469 | 
             
                    next if illegal_header_key?(k)
         | 
| 356 470 |  | 
| @@ -367,66 +481,78 @@ module Puma | |
| 367 481 | 
             
                end
         | 
| 368 482 | 
             
                private :str_early_hints
         | 
| 369 483 |  | 
| 484 | 
            +
                # @param status [Integer] status from the app
         | 
| 485 | 
            +
                # @return [String] the text description from Puma::HTTP_STATUS_CODES
         | 
| 486 | 
            +
                #
         | 
| 487 | 
            +
                def fetch_status_code(status)
         | 
| 488 | 
            +
                  HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
         | 
| 489 | 
            +
                end
         | 
| 490 | 
            +
                private :fetch_status_code
         | 
| 491 | 
            +
             | 
| 370 492 | 
             
                # Processes and write headers to the IOBuffer.
         | 
| 371 493 | 
             
                # @param env [Hash] see Puma::Client#env, from request
         | 
| 372 494 | 
             
                # @param status [Integer] the status returned by the Rack application
         | 
| 373 495 | 
             
                # @param headers [Hash] the headers returned by the Rack application
         | 
| 374 | 
            -
                # @param  | 
| 375 | 
            -
                #  | 
| 496 | 
            +
                # @param content_length [Integer,nil] content length if it can be determined from the
         | 
| 497 | 
            +
                #   response body
         | 
| 498 | 
            +
                # @param io_buffer [Puma::IOBuffer] modified inn place
         | 
| 376 499 | 
             
                # @param requests [Integer] number of inline requests handled
         | 
| 377 500 | 
             
                # @param client [Puma::Client]
         | 
| 501 | 
            +
                # @return [Hash] resp_info
         | 
| 378 502 | 
             
                # @version 5.0.3
         | 
| 379 503 | 
             
                #
         | 
| 380 | 
            -
                def str_headers(env, status, headers,  | 
| 504 | 
            +
                def str_headers(env, status, headers, res_body, io_buffer, requests, client)
         | 
| 381 505 | 
             
                  line_ending = LINE_END
         | 
| 382 506 | 
             
                  colon = COLON
         | 
| 383 507 |  | 
| 384 | 
            -
                   | 
| 508 | 
            +
                  resp_info = {}
         | 
| 509 | 
            +
                  resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
         | 
| 510 | 
            +
             | 
| 511 | 
            +
                  http_11 = env[SERVER_PROTOCOL] == HTTP_11
         | 
| 385 512 | 
             
                  if http_11
         | 
| 386 | 
            -
                     | 
| 387 | 
            -
                     | 
| 513 | 
            +
                    resp_info[:allow_chunked] = true
         | 
| 514 | 
            +
                    resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
         | 
| 388 515 |  | 
| 389 516 | 
             
                    # An optimization. The most common response is 200, so we can
         | 
| 390 517 | 
             
                    # reply with the proper 200 status without having to compute
         | 
| 391 518 | 
             
                    # the response header.
         | 
| 392 519 | 
             
                    #
         | 
| 393 520 | 
             
                    if status == 200
         | 
| 394 | 
            -
                       | 
| 521 | 
            +
                      io_buffer << HTTP_11_200
         | 
| 395 522 | 
             
                    else
         | 
| 396 | 
            -
                       | 
| 397 | 
            -
                                   fetch_status_code(status), line_ending
         | 
| 523 | 
            +
                      io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
         | 
| 398 524 |  | 
| 399 | 
            -
                       | 
| 525 | 
            +
                      resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
         | 
| 400 526 | 
             
                    end
         | 
| 401 527 | 
             
                  else
         | 
| 402 | 
            -
                     | 
| 403 | 
            -
                     | 
| 528 | 
            +
                    resp_info[:allow_chunked] = false
         | 
| 529 | 
            +
                    resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
         | 
| 404 530 |  | 
| 405 531 | 
             
                    # Same optimization as above for HTTP/1.1
         | 
| 406 532 | 
             
                    #
         | 
| 407 533 | 
             
                    if status == 200
         | 
| 408 | 
            -
                       | 
| 534 | 
            +
                      io_buffer << HTTP_10_200
         | 
| 409 535 | 
             
                    else
         | 
| 410 | 
            -
                       | 
| 536 | 
            +
                      io_buffer.append "HTTP/1.0 #{status} ",
         | 
| 411 537 | 
             
                                   fetch_status_code(status), line_ending
         | 
| 412 538 |  | 
| 413 | 
            -
                       | 
| 539 | 
            +
                      resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
         | 
| 414 540 | 
             
                    end
         | 
| 415 541 | 
             
                  end
         | 
| 416 542 |  | 
| 417 543 | 
             
                  # regardless of what the client wants, we always close the connection
         | 
| 418 544 | 
             
                  # if running without request queueing
         | 
| 419 | 
            -
                   | 
| 545 | 
            +
                  resp_info[:keep_alive] &&= @queue_requests
         | 
| 420 546 |  | 
| 421 547 | 
             
                  # Close the connection after a reasonable number of inline requests
         | 
| 422 548 | 
             
                  # if the server is at capacity and the listener has a new connection ready.
         | 
| 423 549 | 
             
                  # This allows Puma to service connections fairly when the number
         | 
| 424 550 | 
             
                  # of concurrent connections exceeds the size of the threadpool.
         | 
| 425 | 
            -
                   | 
| 551 | 
            +
                  resp_info[:keep_alive] &&= requests < @max_fast_inline ||
         | 
| 426 552 | 
             
                    @thread_pool.busy_threads < @max_threads ||
         | 
| 427 553 | 
             
                    !client.listener.to_io.wait_readable(0)
         | 
| 428 554 |  | 
| 429 | 
            -
                   | 
| 555 | 
            +
                  resp_info[:response_hijack] = nil
         | 
| 430 556 |  | 
| 431 557 | 
             
                  headers.each do |k, vs|
         | 
| 432 558 | 
             
                    next if illegal_header_key?(k)
         | 
| @@ -434,25 +560,33 @@ module Puma | |
| 434 560 | 
             
                    case k.downcase
         | 
| 435 561 | 
             
                    when CONTENT_LENGTH2
         | 
| 436 562 | 
             
                      next if illegal_header_value?(vs)
         | 
| 437 | 
            -
                       | 
| 563 | 
            +
                      resp_info[:content_length] = vs
         | 
| 438 564 | 
             
                      next
         | 
| 439 565 | 
             
                    when TRANSFER_ENCODING
         | 
| 440 | 
            -
                       | 
| 441 | 
            -
                       | 
| 566 | 
            +
                      resp_info[:allow_chunked] = false
         | 
| 567 | 
            +
                      resp_info[:content_length] = nil
         | 
| 568 | 
            +
                      resp_info[:transfer_encoding] = vs
         | 
| 442 569 | 
             
                    when HIJACK
         | 
| 443 | 
            -
                       | 
| 570 | 
            +
                      resp_info[:response_hijack] = vs
         | 
| 444 571 | 
             
                      next
         | 
| 445 572 | 
             
                    when BANNED_HEADER_KEY
         | 
| 446 573 | 
             
                      next
         | 
| 447 574 | 
             
                    end
         | 
| 448 575 |  | 
| 449 | 
            -
                    if vs. | 
| 450 | 
            -
                      vs | 
| 576 | 
            +
                    ary = if vs.is_a?(::Array) && !vs.empty?
         | 
| 577 | 
            +
                      vs
         | 
| 578 | 
            +
                    elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
         | 
| 579 | 
            +
                      vs.to_s.split NEWLINE
         | 
| 580 | 
            +
                    else
         | 
| 581 | 
            +
                      nil
         | 
| 582 | 
            +
                    end
         | 
| 583 | 
            +
                    if ary
         | 
| 584 | 
            +
                      ary.each do |v|
         | 
| 451 585 | 
             
                        next if illegal_header_value?(v)
         | 
| 452 | 
            -
                         | 
| 586 | 
            +
                        io_buffer.append k, colon, v, line_ending
         | 
| 453 587 | 
             
                      end
         | 
| 454 588 | 
             
                    else
         | 
| 455 | 
            -
                       | 
| 589 | 
            +
                      io_buffer.append k, colon, line_ending
         | 
| 456 590 | 
             
                    end
         | 
| 457 591 | 
             
                  end
         | 
| 458 592 |  | 
| @@ -462,10 +596,11 @@ module Puma | |
| 462 596 | 
             
                  # Only set the header if we're doing something which is not the default
         | 
| 463 597 | 
             
                  # for this protocol version
         | 
| 464 598 | 
             
                  if http_11
         | 
| 465 | 
            -
                     | 
| 599 | 
            +
                    io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
         | 
| 466 600 | 
             
                  else
         | 
| 467 | 
            -
                     | 
| 601 | 
            +
                    io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
         | 
| 468 602 | 
             
                  end
         | 
| 603 | 
            +
                  resp_info
         | 
| 469 604 | 
             
                end
         | 
| 470 605 | 
             
                private :str_headers
         | 
| 471 606 | 
             
              end
         |