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