tipi 0.38 → 0.42
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.
- checksums.yaml +4 -4
 - data/.github/workflows/test.yml +5 -1
 - data/.gitignore +5 -0
 - data/CHANGELOG.md +34 -0
 - data/Gemfile +5 -1
 - data/Gemfile.lock +58 -16
 - data/Rakefile +7 -3
 - data/TODO.md +77 -1
 - data/benchmarks/bm_http1_parser.rb +61 -0
 - data/bin/benchmark +37 -0
 - data/bin/h1pd +6 -0
 - data/bin/tipi +3 -21
 - data/df/sample_agent.rb +1 -1
 - data/df/server.rb +16 -47
 - data/df/server_utils.rb +178 -0
 - data/examples/full_service.rb +13 -0
 - data/examples/http1_parser.rb +55 -0
 - data/examples/http_server.rb +15 -3
 - data/examples/http_server_forked.rb +5 -1
 - data/examples/http_server_routes.rb +29 -0
 - data/examples/http_server_static.rb +26 -0
 - data/examples/http_server_throttled.rb +3 -2
 - data/examples/https_server.rb +6 -4
 - data/examples/https_wss_server.rb +2 -1
 - data/examples/rack_server.rb +5 -0
 - data/examples/rack_server_https.rb +1 -1
 - data/examples/rack_server_https_forked.rb +4 -3
 - data/examples/routing_server.rb +5 -4
 - data/examples/servername_cb.rb +37 -0
 - data/examples/websocket_demo.rb +2 -8
 - data/examples/ws_page.html +2 -2
 - data/ext/tipi/extconf.rb +13 -0
 - data/ext/tipi/http1_parser.c +823 -0
 - data/ext/tipi/http1_parser.h +18 -0
 - data/ext/tipi/tipi_ext.c +5 -0
 - data/lib/tipi.rb +89 -1
 - data/lib/tipi/acme.rb +308 -0
 - data/lib/tipi/cli.rb +30 -0
 - data/lib/tipi/digital_fabric/agent.rb +22 -17
 - data/lib/tipi/digital_fabric/agent_proxy.rb +95 -40
 - data/lib/tipi/digital_fabric/executive.rb +6 -2
 - data/lib/tipi/digital_fabric/protocol.rb +87 -15
 - data/lib/tipi/digital_fabric/request_adapter.rb +6 -10
 - data/lib/tipi/digital_fabric/service.rb +77 -51
 - data/lib/tipi/http1_adapter.rb +116 -117
 - data/lib/tipi/http2_adapter.rb +56 -10
 - data/lib/tipi/http2_stream.rb +106 -53
 - data/lib/tipi/rack_adapter.rb +2 -53
 - data/lib/tipi/response_extensions.rb +17 -0
 - data/lib/tipi/version.rb +1 -1
 - data/security/http1.rb +12 -0
 - data/test/helper.rb +60 -11
 - data/test/test_http1_parser.rb +586 -0
 - data/test/test_http_server.rb +0 -27
 - data/test/test_request.rb +1 -28
 - data/tipi.gemspec +11 -5
 - metadata +96 -22
 - data/e +0 -0
 
| 
         @@ -6,40 +6,36 @@ module DigitalFabric 
     | 
|
| 
       6 
6 
     | 
    
         
             
              class RequestAdapter
         
     | 
| 
       7 
7 
     | 
    
         
             
                def initialize(agent, msg)
         
     | 
| 
       8 
8 
     | 
    
         
             
                  @agent = agent
         
     | 
| 
       9 
     | 
    
         
            -
                  @id = msg[ 
     | 
| 
      
 9 
     | 
    
         
            +
                  @id = msg[Protocol::Attribute::ID]
         
     | 
| 
       10 
10 
     | 
    
         
             
                end
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                def protocol
         
     | 
| 
       13 
13 
     | 
    
         
             
                  'df'
         
     | 
| 
       14 
14 
     | 
    
         
             
                end
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                def get_body_chunk
         
     | 
| 
      
 16 
     | 
    
         
            +
                def get_body_chunk(request)
         
     | 
| 
       17 
17 
     | 
    
         
             
                  @agent.get_http_request_body(@id, 1)
         
     | 
| 
       18 
18 
     | 
    
         
             
                end
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
                def  
     | 
| 
       21 
     | 
    
         
            -
                  @agent.get_http_request_body(@id, nil)
         
     | 
| 
       22 
     | 
    
         
            -
                end
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                def respond(body, headers)
         
     | 
| 
      
 20 
     | 
    
         
            +
                def respond(request, body, headers)
         
     | 
| 
       25 
21 
     | 
    
         
             
                  @agent.send_df_message(
         
     | 
| 
       26 
22 
     | 
    
         
             
                    Protocol.http_response(@id, body, headers, true)
         
     | 
| 
       27 
23 
     | 
    
         
             
                  )
         
     | 
| 
       28 
24 
     | 
    
         
             
                end
         
     | 
| 
       29 
25 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                def send_headers(headers, opts = {})
         
     | 
| 
      
 26 
     | 
    
         
            +
                def send_headers(request, headers, opts = {})
         
     | 
| 
       31 
27 
     | 
    
         
             
                  @agent.send_df_message(
         
     | 
| 
       32 
28 
     | 
    
         
             
                    Protocol.http_response(@id, nil, headers, false)
         
     | 
| 
       33 
29 
     | 
    
         
             
                  )
         
     | 
| 
       34 
30 
     | 
    
         
             
              end
         
     | 
| 
       35 
31 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
                def send_chunk(body, done: )
         
     | 
| 
      
 32 
     | 
    
         
            +
                def send_chunk(request, body, done: )
         
     | 
| 
       37 
33 
     | 
    
         
             
                  @agent.send_df_message(
         
     | 
| 
       38 
34 
     | 
    
         
             
                    Protocol.http_response(@id, body, nil, done)
         
     | 
| 
       39 
35 
     | 
    
         
             
                  )
         
     | 
| 
       40 
36 
     | 
    
         
             
                end
         
     | 
| 
       41 
37 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
                def finish
         
     | 
| 
      
 38 
     | 
    
         
            +
                def finish(request)
         
     | 
| 
       43 
39 
     | 
    
         
             
                  @agent.send_df_message(
         
     | 
| 
       44 
40 
     | 
    
         
             
                    Protocol.http_response(@id, nil, nil, true)
         
     | 
| 
       45 
41 
     | 
    
         
             
                  )
         
     | 
| 
         @@ -13,26 +13,22 @@ module DigitalFabric 
     | 
|
| 
       13 
13 
     | 
    
         
             
                  @token = token
         
     | 
| 
       14 
14 
     | 
    
         
             
                  @agents = {}
         
     | 
| 
       15 
15 
     | 
    
         
             
                  @routes = {}
         
     | 
| 
       16 
     | 
    
         
            -
                  @waiting_lists = {} # hash mapping routes to arrays of requests waiting for an agent to mount
         
     | 
| 
       17 
16 
     | 
    
         
             
                  @counters = {
         
     | 
| 
       18 
17 
     | 
    
         
             
                    connections: 0,
         
     | 
| 
       19 
18 
     | 
    
         
             
                    http_requests: 0,
         
     | 
| 
       20 
19 
     | 
    
         
             
                    errors: 0
         
     | 
| 
       21 
20 
     | 
    
         
             
                  }
         
     | 
| 
       22 
21 
     | 
    
         
             
                  @connection_count = 0
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @current_request_count = 0
         
     | 
| 
       23 
23 
     | 
    
         
             
                  @http_latency_accumulator = 0
         
     | 
| 
       24 
24 
     | 
    
         
             
                  @http_latency_counter = 0
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @http_latency_max = 0
         
     | 
| 
       25 
26 
     | 
    
         
             
                  @last_counters = @counters.merge(stamp: Time.now.to_f - 1)
         
     | 
| 
       26 
27 
     | 
    
         
             
                  @fiber = Fiber.current
         
     | 
| 
       27 
     | 
    
         
            -
                  @timer = Polyphony::Timer.new(resolution:  
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                  stats_updater = spin { @timer.every(10) { update_stats } }
         
     | 
| 
       30 
     | 
    
         
            -
                  @stats = {}
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                  @current_request_count = 0
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # @timer = Polyphony::Timer.new('service_timer', resolution: 5)
         
     | 
| 
       33 
29 
     | 
    
         
             
                end
         
     | 
| 
       34 
30 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                def  
     | 
| 
      
 31 
     | 
    
         
            +
                def calculate_stats
         
     | 
| 
       36 
32 
     | 
    
         
             
                  now = Time.now.to_f
         
     | 
| 
       37 
33 
     | 
    
         
             
                  elapsed = now - @last_counters[:stamp]
         
     | 
| 
       38 
34 
     | 
    
         
             
                  connections = @counters[:connections] - @last_counters[:connections]
         
     | 
| 
         @@ -40,23 +36,61 @@ module DigitalFabric 
     | 
|
| 
       40 
36 
     | 
    
         
             
                  errors = @counters[:errors] - @last_counters[:errors]
         
     | 
| 
       41 
37 
     | 
    
         
             
                  @last_counters = @counters.merge(stamp: now)
         
     | 
| 
       42 
38 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
                  average_latency = @http_latency_counter  
     | 
| 
       44 
     | 
    
         
            -
                                    @http_latency_accumulator / @http_latency_counter 
     | 
| 
       45 
     | 
    
         
            -
                                    0
         
     | 
| 
      
 39 
     | 
    
         
            +
                  average_latency = @http_latency_counter == 0 ? 0 :
         
     | 
| 
      
 40 
     | 
    
         
            +
                                    @http_latency_accumulator / @http_latency_counter
         
     | 
| 
       46 
41 
     | 
    
         
             
                  @http_latency_accumulator = 0
         
     | 
| 
       47 
42 
     | 
    
         
             
                  @http_latency_counter = 0
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                  @ 
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
      
 43 
     | 
    
         
            +
                  max_latency = @http_latency_max
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @http_latency_max = 0
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  cpu, rss = pid_cpu_and_rss(Process.pid)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  backend_stats = Thread.backend.stats
         
     | 
| 
      
 49 
     | 
    
         
            +
                  op_rate = backend_stats[:op_count] / elapsed
         
     | 
| 
      
 50 
     | 
    
         
            +
                  switch_rate = backend_stats[:switch_count] / elapsed
         
     | 
| 
      
 51 
     | 
    
         
            +
                  poll_rate = backend_stats[:poll_count] / elapsed
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  {
         
     | 
| 
      
 54 
     | 
    
         
            +
                    service: {
         
     | 
| 
      
 55 
     | 
    
         
            +
                      agent_count: @agents.size,
         
     | 
| 
      
 56 
     | 
    
         
            +
                      connection_count: @connection_count,
         
     | 
| 
      
 57 
     | 
    
         
            +
                      connection_rate: connections / elapsed,
         
     | 
| 
      
 58 
     | 
    
         
            +
                      error_rate: errors / elapsed,
         
     | 
| 
      
 59 
     | 
    
         
            +
                      http_request_rate: http_requests / elapsed,
         
     | 
| 
      
 60 
     | 
    
         
            +
                      latency_avg: average_latency,
         
     | 
| 
      
 61 
     | 
    
         
            +
                      latency_max: max_latency,
         
     | 
| 
      
 62 
     | 
    
         
            +
                      pending_requests: @current_request_count,
         
     | 
| 
      
 63 
     | 
    
         
            +
                      },
         
     | 
| 
      
 64 
     | 
    
         
            +
                    backend: {
         
     | 
| 
      
 65 
     | 
    
         
            +
                      op_rate: op_rate,
         
     | 
| 
      
 66 
     | 
    
         
            +
                      pending_ops: backend_stats[:pending_ops],
         
     | 
| 
      
 67 
     | 
    
         
            +
                      poll_rate: poll_rate,
         
     | 
| 
      
 68 
     | 
    
         
            +
                      runqueue_size: backend_stats[:runqueue_size],
         
     | 
| 
      
 69 
     | 
    
         
            +
                      runqueue_high_watermark: backend_stats[:runqueue_max_length],
         
     | 
| 
      
 70 
     | 
    
         
            +
                      switch_rate: switch_rate,
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    },
         
     | 
| 
      
 73 
     | 
    
         
            +
                    process: {
         
     | 
| 
      
 74 
     | 
    
         
            +
                      cpu_usage: cpu,
         
     | 
| 
      
 75 
     | 
    
         
            +
                      rss: rss.to_f / 1024,
         
     | 
| 
      
 76 
     | 
    
         
            +
                    }
         
     | 
| 
       57 
77 
     | 
    
         
             
                  }
         
     | 
| 
       58 
78 
     | 
    
         
             
                end
         
     | 
| 
       59 
79 
     | 
    
         | 
| 
      
 80 
     | 
    
         
            +
                def pid_cpu_and_rss(pid)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  s = `ps -p #{pid} -o %cpu,rss`
         
     | 
| 
      
 82 
     | 
    
         
            +
                  cpu, rss = s.lines[1].chomp.strip.split(' ')
         
     | 
| 
      
 83 
     | 
    
         
            +
                  [cpu.to_f, rss.to_i]
         
     | 
| 
      
 84 
     | 
    
         
            +
                rescue Polyphony::BaseException
         
     | 
| 
      
 85 
     | 
    
         
            +
                  raise
         
     | 
| 
      
 86 
     | 
    
         
            +
                rescue Exception
         
     | 
| 
      
 87 
     | 
    
         
            +
                  [nil, nil]
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
                
         
     | 
| 
      
 90 
     | 
    
         
            +
                def get_stats
         
     | 
| 
      
 91 
     | 
    
         
            +
                  calculate_stats
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
       60 
94 
     | 
    
         
             
                def incr_connection_count
         
     | 
| 
       61 
95 
     | 
    
         
             
                  @connection_count += 1
         
     | 
| 
       62 
96 
     | 
    
         
             
                end
         
     | 
| 
         @@ -77,32 +111,36 @@ module DigitalFabric 
     | 
|
| 
       77 
111 
     | 
    
         
             
                  count
         
     | 
| 
       78 
112 
     | 
    
         
             
                end
         
     | 
| 
       79 
113 
     | 
    
         | 
| 
       80 
     | 
    
         
            -
                def record_latency_measurement(latency)
         
     | 
| 
      
 114 
     | 
    
         
            +
                def record_latency_measurement(latency, req)
         
     | 
| 
       81 
115 
     | 
    
         
             
                  @http_latency_accumulator += latency
         
     | 
| 
       82 
116 
     | 
    
         
             
                  @http_latency_counter += 1
         
     | 
| 
      
 117 
     | 
    
         
            +
                  @http_latency_max = latency if latency > @http_latency_max
         
     | 
| 
      
 118 
     | 
    
         
            +
                  return if latency < 1.0
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  puts format('slow request (%.1f): %p', latency, req.headers)
         
     | 
| 
       83 
121 
     | 
    
         
             
                end
         
     | 
| 
       84 
122 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
                def http_request(req)
         
     | 
| 
      
 123 
     | 
    
         
            +
                def http_request(req, allow_df_upgrade = false)
         
     | 
| 
       86 
124 
     | 
    
         
             
                  @current_request_count += 1
         
     | 
| 
       87 
125 
     | 
    
         
             
                  @counters[:http_requests] += 1
         
     | 
| 
       88 
126 
     | 
    
         
             
                  @counters[:connections] += 1 if req.headers[':first']
         
     | 
| 
       89 
127 
     | 
    
         | 
| 
       90 
     | 
    
         
            -
                  return upgrade_request(req) if req.upgrade_protocol
         
     | 
| 
      
 128 
     | 
    
         
            +
                  return upgrade_request(req, allow_df_upgrade) if req.upgrade_protocol
         
     | 
| 
       91 
129 
     | 
    
         | 
| 
       92 
130 
     | 
    
         
             
                  inject_request_headers(req)
         
     | 
| 
       93 
131 
     | 
    
         
             
                  agent = find_agent(req)
         
     | 
| 
       94 
132 
     | 
    
         
             
                  unless agent
         
     | 
| 
       95 
     | 
    
         
            -
                    return req.respond('pong') if req.query[:q] == 'ping'
         
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
133 
     | 
    
         
             
                    @counters[:errors] += 1
         
     | 
| 
       98 
134 
     | 
    
         
             
                    return req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
         
     | 
| 
       99 
135 
     | 
    
         
             
                  end
         
     | 
| 
       100 
136 
     | 
    
         | 
| 
       101 
137 
     | 
    
         
             
                  agent.http_request(req)
         
     | 
| 
       102 
     | 
    
         
            -
                rescue IOError, SystemCallError
         
     | 
| 
      
 138 
     | 
    
         
            +
                rescue IOError, SystemCallError, HTTP2::Error::StreamClosed
         
     | 
| 
       103 
139 
     | 
    
         
             
                  @counters[:errors] += 1
         
     | 
| 
       104 
140 
     | 
    
         
             
                rescue => e
         
     | 
| 
       105 
141 
     | 
    
         
             
                  @counters[:errors] += 1
         
     | 
| 
      
 142 
     | 
    
         
            +
                  puts '*' * 40
         
     | 
| 
      
 143 
     | 
    
         
            +
                  p req
         
     | 
| 
       106 
144 
     | 
    
         
             
                  p e
         
     | 
| 
       107 
145 
     | 
    
         
             
                  puts e.backtrace.join("\n")
         
     | 
| 
       108 
146 
     | 
    
         
             
                  req.respond(e.inspect, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
         
     | 
| 
         @@ -118,10 +156,14 @@ module DigitalFabric 
     | 
|
| 
       118 
156 
     | 
    
         
             
                  req.headers['x-forwarded-proto'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
         
     | 
| 
       119 
157 
     | 
    
         
             
                end
         
     | 
| 
       120 
158 
     | 
    
         | 
| 
       121 
     | 
    
         
            -
                def upgrade_request(req)
         
     | 
| 
      
 159 
     | 
    
         
            +
                def upgrade_request(req, allow_df_upgrade)
         
     | 
| 
       122 
160 
     | 
    
         
             
                  case (protocol = req.upgrade_protocol)
         
     | 
| 
       123 
161 
     | 
    
         
             
                  when 'df'
         
     | 
| 
       124 
     | 
    
         
            -
                     
     | 
| 
      
 162 
     | 
    
         
            +
                    if allow_df_upgrade
         
     | 
| 
      
 163 
     | 
    
         
            +
                      df_upgrade(req)
         
     | 
| 
      
 164 
     | 
    
         
            +
                    else
         
     | 
| 
      
 165 
     | 
    
         
            +
                      req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
         
     | 
| 
      
 166 
     | 
    
         
            +
                    end
         
     | 
| 
       125 
167 
     | 
    
         
             
                  else
         
     | 
| 
       126 
168 
     | 
    
         
             
                    agent = find_agent(req)
         
     | 
| 
       127 
169 
     | 
    
         
             
                    unless agent
         
     | 
| 
         @@ -134,12 +176,16 @@ module DigitalFabric 
     | 
|
| 
       134 
176 
     | 
    
         
             
                end
         
     | 
| 
       135 
177 
     | 
    
         | 
| 
       136 
178 
     | 
    
         
             
                def df_upgrade(req)
         
     | 
| 
      
 179 
     | 
    
         
            +
                  # we don't want to count connected agents
         
     | 
| 
      
 180 
     | 
    
         
            +
                  @current_request_count -= 1
         
     | 
| 
       137 
181 
     | 
    
         
             
                  if req.headers['df-token'] != @token
         
     | 
| 
       138 
182 
     | 
    
         
             
                    return req.respond(nil, ':status' => Qeweney::Status::FORBIDDEN)
         
     | 
| 
       139 
183 
     | 
    
         
             
                  end
         
     | 
| 
       140 
184 
     | 
    
         | 
| 
       141 
185 
     | 
    
         
             
                  req.adapter.conn << Protocol.df_upgrade_response
         
     | 
| 
       142 
186 
     | 
    
         
             
                  AgentProxy.new(self, req)
         
     | 
| 
      
 187 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 188 
     | 
    
         
            +
                  @current_request_count += 1
         
     | 
| 
       143 
189 
     | 
    
         
             
                end
         
     | 
| 
       144 
190 
     | 
    
         | 
| 
       145 
191 
     | 
    
         
             
                def mount(route, agent)
         
     | 
| 
         @@ -149,11 +195,6 @@ module DigitalFabric 
     | 
|
| 
       149 
195 
     | 
    
         
             
                  @executive = agent if route[:executive]
         
     | 
| 
       150 
196 
     | 
    
         
             
                  @agents[agent] = route
         
     | 
| 
       151 
197 
     | 
    
         
             
                  @routing_changed = true
         
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
                  if (waiting = @waiting_lists[route])
         
     | 
| 
       154 
     | 
    
         
            -
                    waiting.each { |f| f.schedule(agent) }
         
     | 
| 
       155 
     | 
    
         
            -
                    @waiting_lists.delete(route)
         
     | 
| 
       156 
     | 
    
         
            -
                  end
         
     | 
| 
       157 
198 
     | 
    
         
             
                end
         
     | 
| 
       158 
199 
     | 
    
         | 
| 
       159 
200 
     | 
    
         
             
                def unmount(agent)
         
     | 
| 
         @@ -163,8 +204,6 @@ module DigitalFabric 
     | 
|
| 
       163 
204 
     | 
    
         
             
                  @executive = nil if route[:executive]
         
     | 
| 
       164 
205 
     | 
    
         
             
                  @agents.delete(agent)
         
     | 
| 
       165 
206 
     | 
    
         
             
                  @routing_changed = true
         
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
                  @waiting_lists[route] ||= []
         
     | 
| 
       168 
207 
     | 
    
         
             
                end
         
     | 
| 
       169 
208 
     | 
    
         | 
| 
       170 
209 
     | 
    
         
             
                INVALID_HOST = 'INVALID_HOST'
         
     | 
| 
         @@ -172,7 +211,7 @@ module DigitalFabric 
     | 
|
| 
       172 
211 
     | 
    
         
             
                def find_agent(req)
         
     | 
| 
       173 
212 
     | 
    
         
             
                  compile_agent_routes if @routing_changed
         
     | 
| 
       174 
213 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
                  host = req.headers['host'] || INVALID_HOST
         
     | 
| 
      
 214 
     | 
    
         
            +
                  host = req.headers[':authority'] || req.headers['host'] || INVALID_HOST
         
     | 
| 
       176 
215 
     | 
    
         
             
                  path = req.headers[':path']
         
     | 
| 
       177 
216 
     | 
    
         | 
| 
       178 
217 
     | 
    
         
             
                  route = @route_keys.find do |route|
         
     | 
| 
         @@ -180,12 +219,6 @@ module DigitalFabric 
     | 
|
| 
       180 
219 
     | 
    
         
             
                  end
         
     | 
| 
       181 
220 
     | 
    
         
             
                  return @routes[route] if route
         
     | 
| 
       182 
221 
     | 
    
         | 
| 
       183 
     | 
    
         
            -
                  # search for a known route for an agent that recently unmounted
         
     | 
| 
       184 
     | 
    
         
            -
                  route, wait_list = @waiting_lists.find do |route, _|
         
     | 
| 
       185 
     | 
    
         
            -
                    (host == route[:host]) || (path =~ route[:path_regexp])
         
     | 
| 
       186 
     | 
    
         
            -
                  end
         
     | 
| 
       187 
     | 
    
         
            -
                  return wait_for_agent(wait_list) if route
         
     | 
| 
       188 
     | 
    
         
            -
             
     | 
| 
       189 
222 
     | 
    
         
             
                  nil
         
     | 
| 
       190 
223 
     | 
    
         
             
                end
         
     | 
| 
       191 
224 
     | 
    
         | 
| 
         @@ -200,13 +233,6 @@ module DigitalFabric 
     | 
|
| 
       200 
233 
     | 
    
         
             
                  @route_keys = @routes.keys
         
     | 
| 
       201 
234 
     | 
    
         
             
                end
         
     | 
| 
       202 
235 
     | 
    
         | 
| 
       203 
     | 
    
         
            -
                def wait_for_agent(wait_list)
         
     | 
| 
       204 
     | 
    
         
            -
                  wait_list << Fiber.current
         
     | 
| 
       205 
     | 
    
         
            -
                  @timer.move_on_after(10) { suspend }
         
     | 
| 
       206 
     | 
    
         
            -
                ensure
         
     | 
| 
       207 
     | 
    
         
            -
                  wait_list.delete(self)
         
     | 
| 
       208 
     | 
    
         
            -
                end
         
     | 
| 
       209 
     | 
    
         
            -
             
     | 
| 
       210 
236 
     | 
    
         
             
                def path_regexp(path)
         
     | 
| 
       211 
237 
     | 
    
         
             
                  /^#{path}/
         
     | 
| 
       212 
238 
     | 
    
         
             
                end
         
     | 
| 
         @@ -214,8 +240,8 @@ module DigitalFabric 
     | 
|
| 
       214 
240 
     | 
    
         
             
                def graceful_shutdown
         
     | 
| 
       215 
241 
     | 
    
         
             
                  @shutdown = true
         
     | 
| 
       216 
242 
     | 
    
         
             
                  @agents.keys.each do |agent|
         
     | 
| 
       217 
     | 
    
         
            -
                    if agent.respond_to?(: 
     | 
| 
       218 
     | 
    
         
            -
                      agent. 
     | 
| 
      
 243 
     | 
    
         
            +
                    if agent.respond_to?(:send_shutdown)
         
     | 
| 
      
 244 
     | 
    
         
            +
                      agent.send_shutdown
         
     | 
| 
       219 
245 
     | 
    
         
             
                    else
         
     | 
| 
       220 
246 
     | 
    
         
             
                      @agents.delete(agent)
         
     | 
| 
       221 
247 
     | 
    
         
             
                    end
         
     | 
    
        data/lib/tipi/http1_adapter.rb
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require ' 
     | 
| 
      
 3 
     | 
    
         
            +
            require 'tipi_ext'
         
     | 
| 
       4 
4 
     | 
    
         
             
            require_relative './http2_adapter'
         
     | 
| 
       5 
5 
     | 
    
         
             
            require 'qeweney/request'
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
         @@ -14,119 +14,78 @@ module Tipi 
     | 
|
| 
       14 
14 
     | 
    
         
             
                  @conn = conn
         
     | 
| 
       15 
15 
     | 
    
         
             
                  @opts = opts
         
     | 
| 
       16 
16 
     | 
    
         
             
                  @first = true
         
     | 
| 
       17 
     | 
    
         
            -
                  @parser = :: 
     | 
| 
      
 17 
     | 
    
         
            +
                  @parser = Tipi::HTTP1Parser.new(@conn)
         
     | 
| 
       18 
18 
     | 
    
         
             
                end
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
                def each(&block)
         
     | 
| 
       21 
     | 
    
         
            -
                   
     | 
| 
       22 
     | 
    
         
            -
                     
     | 
| 
      
 21 
     | 
    
         
            +
                  while true
         
     | 
| 
      
 22 
     | 
    
         
            +
                    headers = @parser.parse_headers
         
     | 
| 
      
 23 
     | 
    
         
            +
                    break unless headers
         
     | 
| 
      
 24 
     | 
    
         
            +
                    
         
     | 
| 
      
 25 
     | 
    
         
            +
                    # handle_request returns true if connection is not persistent or was
         
     | 
| 
      
 26 
     | 
    
         
            +
                    # upgraded
         
     | 
| 
      
 27 
     | 
    
         
            +
                    break if handle_request(headers, &block)
         
     | 
| 
       23 
28 
     | 
    
         
             
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                rescue Tipi::HTTP1Parser::Error
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # ignore
         
     | 
| 
       24 
31 
     | 
    
         
             
                rescue SystemCallError, IOError
         
     | 
| 
       25 
32 
     | 
    
         
             
                  # ignore
         
     | 
| 
       26 
33 
     | 
    
         
             
                ensure
         
     | 
| 
       27 
34 
     | 
    
         
             
                  finalize_client_loop
         
     | 
| 
       28 
35 
     | 
    
         
             
                end
         
     | 
| 
       29 
36 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                 
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                   
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                     
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
                def handle_request(headers, &block)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  scheme = (proto = headers['x-forwarded-proto']) ?
         
     | 
| 
      
 39 
     | 
    
         
            +
                            proto.downcase : scheme_from_connection
         
     | 
| 
      
 40 
     | 
    
         
            +
                  headers[':scheme'] = scheme
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @protocol = headers[':protocol']
         
     | 
| 
      
 42 
     | 
    
         
            +
                  if @first
         
     | 
| 
      
 43 
     | 
    
         
            +
                    headers[':first'] = true
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @first = nil
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  
         
     | 
| 
      
 47 
     | 
    
         
            +
                  return true if upgrade_connection(headers, &block)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  
         
     | 
| 
      
 49 
     | 
    
         
            +
                  request = Qeweney::Request.new(headers, self)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  if !@parser.complete?
         
     | 
| 
      
 51 
     | 
    
         
            +
                    request.buffer_body_chunk(@parser.read_body_chunk(true))
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                  block.call(request)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  return !persistent_connection?(headers)
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def persistent_connection?(headers)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  if headers[':protocol'] == 'http/1.1'
         
     | 
| 
      
 59 
     | 
    
         
            +
                    return headers['connection'] != 'close'
         
     | 
| 
      
 60 
     | 
    
         
            +
                  else
         
     | 
| 
      
 61 
     | 
    
         
            +
                    connection = headers['connection']
         
     | 
| 
      
 62 
     | 
    
         
            +
                    return connection && connection != 'close'
         
     | 
| 
       43 
63 
     | 
    
         
             
                  end
         
     | 
| 
       44 
     | 
    
         
            -
                  nil
         
     | 
| 
       45 
64 
     | 
    
         
             
                end
         
     | 
| 
       46 
65 
     | 
    
         | 
| 
       47 
66 
     | 
    
         
             
                def finalize_client_loop
         
     | 
| 
       48 
     | 
    
         
            -
                  # release references to various objects
         
     | 
| 
       49 
     | 
    
         
            -
                  @requests_head = @requests_tail = nil
         
     | 
| 
       50 
67 
     | 
    
         
             
                  @parser = nil
         
     | 
| 
      
 68 
     | 
    
         
            +
                  @splicing_pipe = nil
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
         
     | 
| 
       51 
70 
     | 
    
         
             
                  @conn.close
         
     | 
| 
       52 
71 
     | 
    
         
             
                end
         
     | 
| 
       53 
72 
     | 
    
         | 
| 
       54 
73 
     | 
    
         
             
                # Reads a body chunk for the current request. Transfers control to the parse
         
     | 
| 
       55 
74 
     | 
    
         
             
                # loop, and resumes once the parse_loop has fired the on_body callback
         
     | 
| 
       56 
     | 
    
         
            -
                def get_body_chunk
         
     | 
| 
       57 
     | 
    
         
            -
                  @ 
     | 
| 
       58 
     | 
    
         
            -
                  @next_chunk = nil
         
     | 
| 
       59 
     | 
    
         
            -
                  while !@requests_tail.complete? && (data = @conn.readpartial(8192))
         
     | 
| 
       60 
     | 
    
         
            -
                    @parser << data
         
     | 
| 
       61 
     | 
    
         
            -
                    return @next_chunk if @next_chunk
         
     | 
| 
       62 
     | 
    
         
            -
                    
         
     | 
| 
       63 
     | 
    
         
            -
                    snooze
         
     | 
| 
       64 
     | 
    
         
            -
                  end
         
     | 
| 
       65 
     | 
    
         
            -
                  nil
         
     | 
| 
       66 
     | 
    
         
            -
                ensure
         
     | 
| 
       67 
     | 
    
         
            -
                  @waiting_for_body_chunk = nil
         
     | 
| 
       68 
     | 
    
         
            -
                end
         
     | 
| 
       69 
     | 
    
         
            -
                
         
     | 
| 
       70 
     | 
    
         
            -
                # Waits for the current request to complete. Transfers control to the parse
         
     | 
| 
       71 
     | 
    
         
            -
                # loop, and resumes once the parse_loop has fired the on_message_complete
         
     | 
| 
       72 
     | 
    
         
            -
                # callback
         
     | 
| 
       73 
     | 
    
         
            -
                def consume_request
         
     | 
| 
       74 
     | 
    
         
            -
                  request = @requests_head
         
     | 
| 
       75 
     | 
    
         
            -
                  @conn.recv_loop do |data|
         
     | 
| 
       76 
     | 
    
         
            -
                    @parser << data
         
     | 
| 
       77 
     | 
    
         
            -
                    return if request.complete?
         
     | 
| 
       78 
     | 
    
         
            -
                  end
         
     | 
| 
       79 
     | 
    
         
            -
                end
         
     | 
| 
       80 
     | 
    
         
            -
                
         
     | 
| 
       81 
     | 
    
         
            -
                def protocol
         
     | 
| 
       82 
     | 
    
         
            -
                  version = @parser.http_version
         
     | 
| 
       83 
     | 
    
         
            -
                  "HTTP #{version.join('.')}"
         
     | 
| 
       84 
     | 
    
         
            -
                end
         
     | 
| 
       85 
     | 
    
         
            -
                
         
     | 
| 
       86 
     | 
    
         
            -
                def on_headers_complete(headers)
         
     | 
| 
       87 
     | 
    
         
            -
                  headers = normalize_headers(headers)
         
     | 
| 
       88 
     | 
    
         
            -
                  headers[':path'] = @parser.request_url
         
     | 
| 
       89 
     | 
    
         
            -
                  headers[':method'] = @parser.http_method.downcase
         
     | 
| 
       90 
     | 
    
         
            -
                  scheme = (proto = headers['x-forwarded-proto']) ?
         
     | 
| 
       91 
     | 
    
         
            -
                            proto.downcase : scheme_from_connection
         
     | 
| 
       92 
     | 
    
         
            -
                  headers[':scheme'] = scheme
         
     | 
| 
       93 
     | 
    
         
            -
                  queue_request(Qeweney::Request.new(headers, self))
         
     | 
| 
      
 75 
     | 
    
         
            +
                def get_body_chunk(request, buffered_only = false)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  @parser.read_body_chunk(buffered_only)
         
     | 
| 
       94 
77 
     | 
    
         
             
                end
         
     | 
| 
       95 
78 
     | 
    
         | 
| 
       96 
     | 
    
         
            -
                def  
     | 
| 
       97 
     | 
    
         
            -
                   
     | 
| 
       98 
     | 
    
         
            -
                    k = k.downcase
         
     | 
| 
       99 
     | 
    
         
            -
                    hk = h[k]
         
     | 
| 
       100 
     | 
    
         
            -
                    if hk
         
     | 
| 
       101 
     | 
    
         
            -
                      hk = h[k] = [hk] unless hk.is_a?(Array)
         
     | 
| 
       102 
     | 
    
         
            -
                      v.is_a?(Array) ? hk.concat(v) : hk << v
         
     | 
| 
       103 
     | 
    
         
            -
                    else
         
     | 
| 
       104 
     | 
    
         
            -
                      h[k] = v
         
     | 
| 
       105 
     | 
    
         
            -
                    end
         
     | 
| 
       106 
     | 
    
         
            -
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                def get_body(request)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @parser.read_body
         
     | 
| 
       107 
81 
     | 
    
         
             
                end
         
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
                def  
     | 
| 
       110 
     | 
    
         
            -
                   
     | 
| 
       111 
     | 
    
         
            -
                    @requests_tail.__next__ = request
         
     | 
| 
       112 
     | 
    
         
            -
                    @requests_tail = request
         
     | 
| 
       113 
     | 
    
         
            -
                  else
         
     | 
| 
       114 
     | 
    
         
            -
                    @requests_head = @requests_tail = request
         
     | 
| 
       115 
     | 
    
         
            -
                  end
         
     | 
| 
       116 
     | 
    
         
            -
                end
         
     | 
| 
       117 
     | 
    
         
            -
                
         
     | 
| 
       118 
     | 
    
         
            -
                def on_body(chunk)
         
     | 
| 
       119 
     | 
    
         
            -
                  if @waiting_for_body_chunk
         
     | 
| 
       120 
     | 
    
         
            -
                    @next_chunk = chunk
         
     | 
| 
       121 
     | 
    
         
            -
                    @waiting_for_body_chunk = nil
         
     | 
| 
       122 
     | 
    
         
            -
                  else
         
     | 
| 
       123 
     | 
    
         
            -
                    @requests_tail.buffer_body_chunk(chunk)
         
     | 
| 
       124 
     | 
    
         
            -
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                def complete?(request)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  @parser.complete?
         
     | 
| 
       125 
85 
     | 
    
         
             
                end
         
     | 
| 
       126 
86 
     | 
    
         | 
| 
       127 
     | 
    
         
            -
                def  
     | 
| 
       128 
     | 
    
         
            -
                  @ 
     | 
| 
       129 
     | 
    
         
            -
                  @requests_tail.complete!(@parser.keep_alive?)
         
     | 
| 
      
 87 
     | 
    
         
            +
                def protocol
         
     | 
| 
      
 88 
     | 
    
         
            +
                  @protocol
         
     | 
| 
       130 
89 
     | 
    
         
             
                end
         
     | 
| 
       131 
90 
     | 
    
         | 
| 
       132 
91 
     | 
    
         
             
                # Upgrades the connection to a different protocol, if the 'Upgrade' header is
         
     | 
| 
         @@ -164,14 +123,15 @@ module Tipi 
     | 
|
| 
       164 
123 
     | 
    
         
             
                end
         
     | 
| 
       165 
124 
     | 
    
         | 
| 
       166 
125 
     | 
    
         
             
                def upgrade_with_handler(handler, headers)
         
     | 
| 
       167 
     | 
    
         
            -
                  @parser =  
     | 
| 
      
 126 
     | 
    
         
            +
                  @parser = nil
         
     | 
| 
       168 
127 
     | 
    
         
             
                  handler.(self, headers)
         
     | 
| 
       169 
128 
     | 
    
         
             
                  true
         
     | 
| 
       170 
129 
     | 
    
         
             
                end
         
     | 
| 
       171 
130 
     | 
    
         | 
| 
       172 
131 
     | 
    
         
             
                def upgrade_to_http2(headers, &block)
         
     | 
| 
       173 
     | 
    
         
            -
                   
     | 
| 
       174 
     | 
    
         
            -
                   
     | 
| 
      
 132 
     | 
    
         
            +
                  headers = http2_upgraded_headers(headers)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  body = @parser.read_body
         
     | 
| 
      
 134 
     | 
    
         
            +
                  HTTP2Adapter.upgrade_each(@conn, @opts, headers, body, &block)
         
     | 
| 
       175 
135 
     | 
    
         
             
                  true
         
     | 
| 
       176 
136 
     | 
    
         
             
                end
         
     | 
| 
       177 
137 
     | 
    
         | 
| 
         @@ -185,8 +145,8 @@ module Tipi 
     | 
|
| 
       185 
145 
     | 
    
         
             
                  )
         
     | 
| 
       186 
146 
     | 
    
         
             
                end
         
     | 
| 
       187 
147 
     | 
    
         | 
| 
       188 
     | 
    
         
            -
                def websocket_connection( 
     | 
| 
       189 
     | 
    
         
            -
                  Tipi::Websocket.new(@conn,  
     | 
| 
      
 148 
     | 
    
         
            +
                def websocket_connection(request)
         
     | 
| 
      
 149 
     | 
    
         
            +
                  Tipi::Websocket.new(@conn, request.headers)
         
     | 
| 
       190 
150 
     | 
    
         
             
                end
         
     | 
| 
       191 
151 
     | 
    
         | 
| 
       192 
152 
     | 
    
         
             
                def scheme_from_connection
         
     | 
| 
         @@ -200,61 +160,100 @@ module Tipi 
     | 
|
| 
       200 
160 
     | 
    
         | 
| 
       201 
161 
     | 
    
         
             
                # Sends response including headers and body. Waits for the request to complete
         
     | 
| 
       202 
162 
     | 
    
         
             
                # if not yet completed. The body is sent using chunked transfer encoding.
         
     | 
| 
      
 163 
     | 
    
         
            +
                # @param request [Qeweney::Request] HTTP request
         
     | 
| 
       203 
164 
     | 
    
         
             
                # @param body [String] response body
         
     | 
| 
       204 
165 
     | 
    
         
             
                # @param headers
         
     | 
| 
       205 
     | 
    
         
            -
                def respond(body, headers)
         
     | 
| 
       206 
     | 
    
         
            -
                   
     | 
| 
       207 
     | 
    
         
            -
                   
     | 
| 
       208 
     | 
    
         
            -
             
     | 
| 
       209 
     | 
    
         
            -
             
     | 
| 
       210 
     | 
    
         
            -
                   
     | 
| 
       211 
     | 
    
         
            -
             
     | 
| 
       212 
     | 
    
         
            -
                   
     | 
| 
       213 
     | 
    
         
            -
                  #     # data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
         
     | 
| 
       214 
     | 
    
         
            -
                  #     data << "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
         
     | 
| 
       215 
     | 
    
         
            -
                  #   end
         
     | 
| 
       216 
     | 
    
         
            -
                  # end
         
     | 
| 
       217 
     | 
    
         
            -
                  # Polyphony.backend_sendv(@conn, data, 0)
         
     | 
| 
       218 
     | 
    
         
            -
                  @conn.write(*data)
         
     | 
| 
      
 166 
     | 
    
         
            +
                def respond(request, body, headers)
         
     | 
| 
      
 167 
     | 
    
         
            +
                  formatted_headers = format_headers(headers, body, false)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
         
     | 
| 
      
 169 
     | 
    
         
            +
                  if body
         
     | 
| 
      
 170 
     | 
    
         
            +
                    @conn.write(formatted_headers, body)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  else
         
     | 
| 
      
 172 
     | 
    
         
            +
                    @conn.write(formatted_headers)
         
     | 
| 
      
 173 
     | 
    
         
            +
                  end
         
     | 
| 
       219 
174 
     | 
    
         
             
                end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def respond_from_io(request, io, headers, chunk_size = 2**14)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  formatted_headers = format_headers(headers, true, true)
         
     | 
| 
      
 178 
     | 
    
         
            +
                  request.tx_incr(formatted_headers.bytesize)
         
     | 
| 
       220 
179 
     | 
    
         | 
| 
      
 180 
     | 
    
         
            +
                  # assume chunked encoding
         
     | 
| 
      
 181 
     | 
    
         
            +
                  Thread.current.backend.splice_chunks(
         
     | 
| 
      
 182 
     | 
    
         
            +
                    io,
         
     | 
| 
      
 183 
     | 
    
         
            +
                    @conn,
         
     | 
| 
      
 184 
     | 
    
         
            +
                    formatted_headers,
         
     | 
| 
      
 185 
     | 
    
         
            +
                    "0\r\n\r\n",
         
     | 
| 
      
 186 
     | 
    
         
            +
                    ->(len) { "#{len.to_s(16)}\r\n" },
         
     | 
| 
      
 187 
     | 
    
         
            +
                    "\r\n",
         
     | 
| 
      
 188 
     | 
    
         
            +
                    chunk_size
         
     | 
| 
      
 189 
     | 
    
         
            +
                  )
         
     | 
| 
      
 190 
     | 
    
         
            +
                end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
       221 
192 
     | 
    
         
             
                # Sends response headers. If empty_response is truthy, the response status
         
     | 
| 
       222 
193 
     | 
    
         
             
                # code will default to 204, otherwise to 200.
         
     | 
| 
      
 194 
     | 
    
         
            +
                # @param request [Qeweney::Request] HTTP request
         
     | 
| 
       223 
195 
     | 
    
         
             
                # @param headers [Hash] response headers
         
     | 
| 
       224 
196 
     | 
    
         
             
                # @param empty_response [boolean] whether a response body will be sent
         
     | 
| 
       225 
197 
     | 
    
         
             
                # @param chunked [boolean] whether to use chunked transfer encoding
         
     | 
| 
       226 
198 
     | 
    
         
             
                # @return [void]
         
     | 
| 
       227 
     | 
    
         
            -
                def send_headers(headers, empty_response: false, chunked: true)
         
     | 
| 
       228 
     | 
    
         
            -
                   
     | 
| 
       229 
     | 
    
         
            -
                   
     | 
| 
      
 199 
     | 
    
         
            +
                def send_headers(request, headers, empty_response: false, chunked: true)
         
     | 
| 
      
 200 
     | 
    
         
            +
                  formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  request.tx_incr(formatted_headers.bytesize)
         
     | 
| 
      
 202 
     | 
    
         
            +
                  @conn.write(formatted_headers)
         
     | 
| 
      
 203 
     | 
    
         
            +
                end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                def http1_1?(request)
         
     | 
| 
      
 206 
     | 
    
         
            +
                  request.headers[':protocol'] == 'http/1.1'
         
     | 
| 
       230 
207 
     | 
    
         
             
                end
         
     | 
| 
       231 
208 
     | 
    
         | 
| 
       232 
209 
     | 
    
         
             
                # Sends a response body chunk. If no headers were sent, default headers are
         
     | 
| 
       233 
210 
     | 
    
         
             
                # sent using #send_headers. if the done option is true(thy), an empty chunk
         
     | 
| 
       234 
211 
     | 
    
         
             
                # will be sent to signal response completion to the client.
         
     | 
| 
      
 212 
     | 
    
         
            +
                # @param request [Qeweney::Request] HTTP request
         
     | 
| 
       235 
213 
     | 
    
         
             
                # @param chunk [String] response body chunk
         
     | 
| 
       236 
214 
     | 
    
         
             
                # @param done [boolean] whether the response is completed
         
     | 
| 
       237 
215 
     | 
    
         
             
                # @return [void]
         
     | 
| 
       238 
     | 
    
         
            -
                def send_chunk(chunk, done: false)
         
     | 
| 
       239 
     | 
    
         
            -
                  data =  
     | 
| 
      
 216 
     | 
    
         
            +
                def send_chunk(request, chunk, done: false)
         
     | 
| 
      
 217 
     | 
    
         
            +
                  data = +''
         
     | 
| 
       240 
218 
     | 
    
         
             
                  data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
         
     | 
| 
       241 
219 
     | 
    
         
             
                  data << "0\r\n\r\n" if done
         
     | 
| 
       242 
     | 
    
         
            -
                   
     | 
| 
      
 220 
     | 
    
         
            +
                  return if data.empty?
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                  request.tx_incr(data.bytesize)
         
     | 
| 
      
 223 
     | 
    
         
            +
                  @conn.write(data)
         
     | 
| 
       243 
224 
     | 
    
         
             
                end
         
     | 
| 
       244 
225 
     | 
    
         | 
| 
      
 226 
     | 
    
         
            +
                def send_chunk_from_io(request, io, r, w, chunk_size)
         
     | 
| 
      
 227 
     | 
    
         
            +
                  len = w.splice(io, chunk_size)
         
     | 
| 
      
 228 
     | 
    
         
            +
                  if len > 0
         
     | 
| 
      
 229 
     | 
    
         
            +
                    Thread.current.backend.chain(
         
     | 
| 
      
 230 
     | 
    
         
            +
                      [:write, @conn, "#{len.to_s(16)}\r\n"],
         
     | 
| 
      
 231 
     | 
    
         
            +
                      [:splice, r, @conn, len],
         
     | 
| 
      
 232 
     | 
    
         
            +
                      [:write, @conn, "\r\n"]
         
     | 
| 
      
 233 
     | 
    
         
            +
                    )
         
     | 
| 
      
 234 
     | 
    
         
            +
                  else
         
     | 
| 
      
 235 
     | 
    
         
            +
                    @conn.write("0\r\n\r\n")
         
     | 
| 
      
 236 
     | 
    
         
            +
                  end
         
     | 
| 
      
 237 
     | 
    
         
            +
                  len
         
     | 
| 
      
 238 
     | 
    
         
            +
                end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
       245 
240 
     | 
    
         
             
                # Finishes the response to the current request. If no headers were sent,
         
     | 
| 
       246 
241 
     | 
    
         
             
                # default headers are sent using #send_headers.
         
     | 
| 
       247 
242 
     | 
    
         
             
                # @return [void]
         
     | 
| 
       248 
     | 
    
         
            -
                def finish
         
     | 
| 
      
 243 
     | 
    
         
            +
                def finish(request)
         
     | 
| 
      
 244 
     | 
    
         
            +
                  request.tx_incr(5)
         
     | 
| 
       249 
245 
     | 
    
         
             
                  @conn << "0\r\n\r\n"
         
     | 
| 
       250 
246 
     | 
    
         
             
                end
         
     | 
| 
       251 
247 
     | 
    
         | 
| 
       252 
248 
     | 
    
         
             
                def close
         
     | 
| 
      
 249 
     | 
    
         
            +
                  @conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
         
     | 
| 
       253 
250 
     | 
    
         
             
                  @conn.close
         
     | 
| 
       254 
251 
     | 
    
         
             
                end
         
     | 
| 
       255 
252 
     | 
    
         | 
| 
       256 
253 
     | 
    
         
             
                private
         
     | 
| 
       257 
254 
     | 
    
         | 
| 
      
 255 
     | 
    
         
            +
                INTERNAL_HEADER_REGEXP = /^:/.freeze
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
       258 
257 
     | 
    
         
             
                # Formats response headers into an array. If empty_response is true(thy),
         
     | 
| 
       259 
258 
     | 
    
         
             
                # the response status code will default to 204, otherwise to 200.
         
     | 
| 
       260 
259 
     | 
    
         
             
                # @param headers [Hash] response headers
         
     | 
| 
         @@ -266,7 +265,7 @@ module Tipi 
     | 
|
| 
       266 
265 
     | 
    
         
             
                  status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
         
     | 
| 
       267 
266 
     | 
    
         
             
                  lines = format_status_line(body, status, chunked)
         
     | 
| 
       268 
267 
     | 
    
         
             
                  headers.each do |k, v|
         
     | 
| 
       269 
     | 
    
         
            -
                    next if k =~  
     | 
| 
      
 268 
     | 
    
         
            +
                    next if k =~ INTERNAL_HEADER_REGEXP
         
     | 
| 
       270 
269 
     | 
    
         | 
| 
       271 
270 
     | 
    
         
             
                    collect_header_lines(lines, k, v)
         
     | 
| 
       272 
271 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -294,7 +293,7 @@ module Tipi 
     | 
|
| 
       294 
293 
     | 
    
         
             
                  if chunked
         
     | 
| 
       295 
294 
     | 
    
         
             
                    +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
         
     | 
| 
       296 
295 
     | 
    
         
             
                  else
         
     | 
| 
       297 
     | 
    
         
            -
                    +"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
         
     | 
| 
      
 296 
     | 
    
         
            +
                    +"HTTP/1.1 #{status}\r\nContent-Length: #{body.is_a?(String) ? body.bytesize : body.to_i}\r\n"
         
     | 
| 
       298 
297 
     | 
    
         
             
                  end
         
     | 
| 
       299 
298 
     | 
    
         
             
                end
         
     | 
| 
       300 
299 
     | 
    
         |