File: http_request.rb

package info (click to toggle)
ruby-azure-storage-common 2.0.1-8
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 496 kB
  • sloc: ruby: 3,022; makefile: 7
file content (194 lines) | stat: -rw-r--r-- 7,209 bytes parent folder | download | duplicates (3)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#-------------------------------------------------------------------------
# # Copyright (c) Microsoft and contributors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#--------------------------------------------------------------------------
require 'digest/md5'
require 'base64'
require 'net/http'
require 'time'

require 'azure/core/version'
require 'azure/core/http/http_response'
require 'azure/core/http/retry_policy'
require 'azure/core/default'
require 'azure/http_response_helper'

module Azure
  module Core
    module Http
      # Represents a HTTP request can perform synchronous queries to a
      # HTTP server, returning a HttpResponse
      class HttpRequest
        include Azure::HttpResponseHelper
        alias_method :_method, :method

        # The HTTP method to use (:get, :post, :put, :delete, etc...)
        attr_accessor :method

        # The URI of the HTTP endpoint to query
        attr_accessor :uri

        # The header values as a Hash
        attr_accessor :headers

        # The body of the request (IO or String)
        attr_accessor :body

        # Azure client which contains configuration context and http agents
        # @return [Azure::Client]
        attr_accessor :client
        
        # The http filter
        attr_accessor :has_retry_filter

        # Public: Create the HttpRequest
        #
        # @param method   [Symbol] The HTTP method to use (:get, :post, :put, :del, etc...)
        # @param uri      [URI] The URI of the HTTP endpoint to query
        # @param options_or_body  [Hash|IO|String] The request options including {:client, :body} or raw body only
        def initialize(method, uri, options_or_body = {})
          options ||= unless options_or_body.is_a?(Hash)
                        {body: options_or_body}
                      end || options_or_body || {}

          @method = method
          @uri = if uri.is_a?(String)
                   URI.parse(uri)
                 else
                   uri
                 end

          @client = options[:client] || Azure

          self.headers = default_headers(options[:current_time] || Time.now.httpdate).merge(options[:headers] || {})
          self.body = options[:body]
        end

        # Public: Applies a HttpFilter to the HTTP Pipeline
        #
        # filter - Any object that responds to .call(req, _next) and
        #          returns a HttpResponse eg. HttpFilter, Proc,
        #          lambda, etc. (optional)
        #
        # options - The options that are used when call Azure::Core::FilteredService.call.
        #           It can be used by retry policies to determine changes in the retry.
        #
        # &block - An inline block may be used instead of a filter
        #
        #          example:
        #
        #             request.with_filter do |req, _next|
        #               _next.call
        #             end
        #
        # NOTE:
        #
        # The code block provided must call _next or the filter pipeline
        # will not complete and the HTTP request will never execute
        #
        def with_filter(filter=nil, options={}, &block)
          filter = filter || block
          if filter
            is_retry_policy = filter.is_a?(Azure::Core::Http::RetryPolicy)
            filter.retry_data[:request_options] = options if is_retry_policy
            @has_retry_filter ||= is_retry_policy
            
            original_call = self._method(:call)

            # support 1.8.7 (define_singleton_method doesn't exist until 1.9.1)
            filter_call = Proc.new do
              filter.call(self, original_call)
            end
            k = class << self;
              self;
            end
            if k.method_defined? :define_singleton_method
              self.define_singleton_method(:call, filter_call)
            else
              k.send(:define_method, :call, filter_call)
            end
          end
        end

        # Build a default headers Hash
        def default_headers(current_time)
          {}.tap do |def_headers|
            def_headers['User-Agent'] = Azure::Core::Default::USER_AGENT
            def_headers['x-ms-date'] = current_time
            def_headers['x-ms-version'] = '2014-02-14'
            def_headers['DataServiceVersion'] = '1.0;NetFx'
            def_headers['MaxDataServiceVersion'] = '3.0;NetFx'
            def_headers['Content-Type'] = 'application/atom+xml; charset=utf-8'
          end
        end

        def http_setup
          @client.agents(uri)
        end

        def body=(body)
          @body = body
          apply_body_headers
        end

        # Sends request to HTTP server and returns a HttpResponse
        #
        # @return [HttpResponse]
        def call
          conn = http_setup
          res = set_up_response(method.to_sym, uri, conn, headers ,body)

          response = HttpResponse.new(res)
          response.uri = uri
          raise response.error if !response.success? && !@has_retry_filter
          response
        end

        private

        def apply_body_headers
          return headers['Content-Length'] = '0' unless body

          return apply_io_headers        if IO === body || Tempfile === body
          return apply_string_io_headers if StringIO === body
          return apply_miscellaneous_headers
        end

        def apply_io_headers
          headers['Content-Length'] = body.size.to_s if body.respond_to?('size')
          if headers['Content-Length'].nil?
            raise ArgumentError, '\'Content-Length\' must be defined if size cannot be obtained from body IO.'
          end
          headers['Content-MD5'] = Digest::MD5.file(body.path).base64digest unless headers['Content-MD5']
        end

        def apply_string_io_headers
          headers['Content-Length'] = body.size.to_s
          unless headers['Content-MD5']
            headers['Content-MD5'] = Digest::MD5.new.tap do |checksum|
                                       while chunk = body.read(5242880)
                                         checksum << chunk
                                       end
                                       body.rewind
                                     end.base64digest
          end
        end

        def apply_miscellaneous_headers
          headers['Content-Length'] = body.size.to_s
          headers['Content-MD5'] = Base64.strict_encode64(Digest::MD5.digest(body.to_s)) unless headers['Content-MD5']
        end
      end
    end
  end
end