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