File: h2c.rb

package info (click to toggle)
ruby-httpx 1.7.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,816 kB
  • sloc: ruby: 12,209; makefile: 4
file content (127 lines) | stat: -rw-r--r-- 3,389 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# frozen_string_literal: true

module HTTPX
  module Plugins
    #
    # This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
    # (https://datatracker.ietf.org/doc/html/rfc7540#section-3.2)
    #
    # https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2c
    #
    module H2C
      VALID_H2C_VERBS = %w[GET OPTIONS HEAD].freeze

      class << self
        def load_dependencies(klass)
          klass.plugin(:upgrade)
        end

        def call(connection, request, response)
          connection.upgrade_to_h2c(request, response)
        end

        def extra_options(options)
          options.merge(
            h2c_class: Class.new(options.http2_class) { include(H2CParser) },
            max_concurrent_requests: 1,
            upgrade_handlers: options.upgrade_handlers.merge("h2c" => self),
          )
        end
      end

      module OptionsMethods
        def option_h2c_class(value)
          value
        end
      end

      module RequestMethods
        def valid_h2c_verb?
          VALID_H2C_VERBS.include?(@verb)
        end
      end

      module ConnectionMethods
        using URIExtensions

        def initialize(*)
          super
          @h2c_handshake = false
        end

        def send(request)
          return super if @h2c_handshake

          return super unless request.valid_h2c_verb? && request.scheme == "http"

          return super if @upgrade_protocol == "h2c"

          @h2c_handshake = true

          # build upgrade request
          request.headers.add("connection", "upgrade")
          request.headers.add("connection", "http2-settings")
          request.headers["upgrade"] = "h2c"
          request.headers["http2-settings"] = ::HTTP2::Client.settings_header(request.options.http2_settings)

          super
        end

        def upgrade_to_h2c(request, response)
          prev_parser = @parser

          if prev_parser
            prev_parser.reset
            @inflight -= prev_parser.requests.size
          end

          @parser = request.options.h2c_class.new(@write_buffer, @options)
          set_parser_callbacks(@parser)
          @inflight += 1
          @parser.upgrade(request, response)
          @upgrade_protocol = "h2c"

          prev_parser.requests.each do |req|
            req.transition(:idle)
            send(req)
          end
        end

        private

        def send_request_to_parser(request)
          super

          return unless request.headers["upgrade"] == "h2c" && parser.is_a?(Connection::HTTP1)

          max_concurrent_requests = parser.max_concurrent_requests

          return if max_concurrent_requests == 1

          parser.max_concurrent_requests = 1
          request.once(:response) do
            parser.max_concurrent_requests = max_concurrent_requests
          end
        end
      end

      module H2CParser
        def upgrade(request, response)
          # skip checks, it is assumed that this is the first
          # request in the connection
          stream = @connection.upgrade

          # on_settings
          handle_stream(stream, request)
          @streams[request] = stream

          # clean up data left behind in the buffer, if the server started
          # sending frames
          data = response.read
          @connection << data
        end
      end
    end
    register_plugin(:h2c, H2C)
  end
end