File: expect.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 (120 lines) | stat: -rw-r--r-- 3,637 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
# frozen_string_literal: true

module HTTPX
  module Plugins
    #
    # This plugin makes all HTTP/1.1 requests with a body send the "Expect: 100-continue".
    #
    # https://gitlab.com/os85/httpx/wikis/Expect#expect
    #
    module Expect
      EXPECT_TIMEOUT = 2

      class << self
        def no_expect_store
          @no_expect_store ||= []
        end

        def extra_options(options)
          options.merge(expect_timeout: EXPECT_TIMEOUT)
        end
      end

      # adds support for the following options:
      #
      # :expect_timeout :: time (in seconds) to wait for a 100-expect response,
      #                    before retrying without the Expect header (defaults to <tt>2</tt>).
      # :expect_threshold_size :: min threshold (in bytes) of the request payload to enable the 100-continue negotiation on.
      module OptionsMethods
        private

        def option_expect_timeout(value)
          seconds = Float(value)
          raise TypeError, ":expect_timeout must be positive" unless seconds.positive?

          seconds
        end

        def option_expect_threshold_size(value)
          bytes = Integer(value)
          raise TypeError, ":expect_threshold_size must be positive" unless bytes.positive?

          bytes
        end
      end

      module RequestMethods
        def initialize(*)
          super
          return if @body.empty?

          threshold = @options.expect_threshold_size
          return if threshold && !@body.unbounded_body? && @body.bytesize < threshold

          return if Expect.no_expect_store.include?(origin)

          @headers["expect"] = "100-continue"
        end

        def response=(response)
          if response.is_a?(Response) &&
             response.status == 100 &&
             !@headers.key?("expect") &&
             (@state == :body || @state == :done)

            # if we're past this point, this means that we just received a 100-Continue response,
            # but the request doesn't have the expect flag, and is already flushing (or flushed) the body.
            #
            # this means that expect was deactivated for this request too soon, i.e. response took longer.
            #
            # so we have to reactivate it again.
            @headers["expect"] = "100-continue"
            @informational_status = 100
            Expect.no_expect_store.delete(origin)
          end
          super
        end
      end

      module ConnectionMethods
        def send_request_to_parser(request)
          super

          return unless request.headers["expect"] == "100-continue"

          expect_timeout = request.options.expect_timeout

          return if expect_timeout.nil? || expect_timeout.infinite?

          set_request_timeout(:expect_timeout, request, expect_timeout, :expect, %i[body response]) do
            # expect timeout expired
            if request.state == :expect && !request.expects?
              Expect.no_expect_store << request.origin
              request.headers.delete("expect")
              consume
            end
          end
        end
      end

      module InstanceMethods
        def fetch_response(request, selector, options)
          response = super

          return unless response

          if response.is_a?(Response) && response.status == 417 && request.headers.key?("expect")
            response.close
            request.headers.delete("expect")
            request.transition(:idle)
            send_request(request, selector, options)
            return
          end

          response
        end
      end
    end
    register_plugin :expect, Expect
  end
end