File: body.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 (158 lines) | stat: -rw-r--r-- 4,606 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# frozen_string_literal: true

module HTTPX
  # Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
  class Request::Body < SimpleDelegator
    class << self
      def new(_, options, body: nil, **params)
        if body.is_a?(self)
          # request derives its options from body
          body.options = options.merge(params)
          return body
        end

        super
      end
    end

    attr_accessor :options

    # inits the instance with the request +headers+, +options+ and +params+, which contain the payload definition.
    # it wraps the given body with the appropriate encoder on initialization.
    #
    #   ..., json: { foo: "bar" }) #=> json encoder
    #   ..., form: { foo: "bar" }) #=> form urlencoded encoder
    #   ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
    #   ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
    #   ..., form: { body: "bla") }) #=> raw data encoder
    def initialize(h, options, **params)
      @headers = h
      @body = self.class.initialize_body(params)
      @options = options.merge(params)

      if @body
        if @options.compress_request_body && @headers.key?("content-encoding")

          @headers.get("content-encoding").each do |encoding|
            @body = self.class.initialize_deflater_body(@body, encoding)
          end
        end

        @headers["content-type"] ||= @body.content_type
        @headers["content-length"] = @body.bytesize unless unbounded_body?
      end

      super(@body)
    end

    # consumes and yields the request payload in chunks.
    def each(&block)
      return enum_for(__method__) unless block
      return if @body.nil?

      body = stream(@body)
      if body.respond_to?(:read)
        while (chunk = body.read(16_384))
          block.call(chunk)
        end
        # TODO: use copy_stream once bug is resolved: https://bugs.ruby-lang.org/issues/21131
        # IO.copy_stream(body, ProcIO.new(block))
      elsif body.respond_to?(:each)
        body.each(&block)
      else
        block[body.to_s]
      end
    end

    def close
      @body.close if @body.respond_to?(:close)
    end

    # if the +@body+ is rewindable, it rewinnds it.
    def rewind
      return if empty?

      @body.rewind if @body.respond_to?(:rewind)
    end

    # return +true+ if the +body+ has been fully drained (or does nnot exist).
    def empty?
      return true if @body.nil?
      return false if chunked?

      @body.bytesize.zero?
    end

    # returns the +@body+ payload size in bytes.
    def bytesize
      return 0 if @body.nil?

      @body.bytesize
    end

    # sets the body to yield using chunked trannsfer encoding format.
    def stream(body)
      return body unless chunked?

      Transcoder::Chunker.encode(body.enum_for(:each))
    end

    # returns whether the body yields infinitely.
    def unbounded_body?
      return @unbounded_body if defined?(@unbounded_body)

      @unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
    end

    # returns whether the chunked transfer encoding header is set.
    def chunked?
      @headers["transfer-encoding"] == "chunked"
    end

    # sets the chunked transfer encoding header.
    def chunk!
      @headers.add("transfer-encoding", "chunked")
    end

    # :nocov:
    def inspect
      "#<#{self.class}:#{object_id} " \
        "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
    end
    # :nocov:

    class << self
      def initialize_body(params)
        if (body = params.delete(:body))
          # @type var body: bodyIO
          Transcoder::Body.encode(body)
        elsif (form = params.delete(:form))
          if Transcoder::Multipart.multipart?(form)
            # @type var form: Transcoder::Multipart::multipart_input
            Transcoder::Multipart.encode(form)
          else
            # @type var form: Transcoder::urlencoded_input
            Transcoder::Form.encode(form)
          end
        elsif (json = params.delete(:json))
          # @type var body: _ToJson
          Transcoder::JSON.encode(json)
        end
      end

      # returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
      def initialize_deflater_body(body, encoding)
        case encoding
        when "gzip"
          Transcoder::GZIP.encode(body)
        when "deflate"
          Transcoder::Deflate.encode(body)
        when "identity"
          body
        else
          body
        end
      end
    end
  end
end