File: push_promise.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 (81 lines) | stat: -rw-r--r-- 2,393 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
# frozen_string_literal: true

module HTTPX
  module Plugins
    #
    # This plugin adds support for HTTP/2 Push responses.
    #
    # In order to benefit from this, requests are sent one at a time, so that
    # no push responses are received after corresponding request has been sent.
    #
    # https://gitlab.com/os85/httpx/wikis/Server-Push
    #
    module PushPromise
      def self.extra_options(options)
        options.merge(http2_settings: { settings_enable_push: 1 },
                      max_concurrent_requests: 1)
      end

      module ResponseMethods
        def pushed?
          @__pushed
        end

        def mark_as_pushed!
          @__pushed = true
        end
      end

      module InstanceMethods
        private

        def promise_headers
          @promise_headers ||= {}
        end

        def on_promise(parser, stream)
          stream.on(:promise_headers) do |h|
            __on_promise_request(parser, stream, h)
          end
          stream.on(:headers) do |h|
            __on_promise_response(parser, stream, h)
          end
        end

        def __on_promise_request(parser, stream, h)
          log(level: 1, color: :yellow) do
            # :nocov:
            h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
            # :nocov:
          end
          headers = @options.headers_class.new(h)
          path = headers[":path"]
          authority = headers[":authority"]

          request = parser.pending.find { |r| r.authority == authority && r.path == path }
          if request
            request.merge_headers(headers)
            promise_headers[stream] = request
            parser.pending.delete(request)
            parser.streams[request] = stream
            request.transition(:done)
          else
            stream.refuse
          end
        end

        def __on_promise_response(parser, stream, h)
          request = promise_headers.delete(stream)
          return unless request

          parser.__send__(:on_stream_headers, stream, request, h)
          response = request.response
          response.mark_as_pushed!
          stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
          stream.on(:close, &parser.method(:on_stream_close).curry(3)[stream, request])
        end
      end
    end
    register_plugin(:push_promise, PushPromise)
  end
end