#------------------------------------------------------------------------- # # 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) # # &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, &block) filter = filter || block if filter @has_retry_filter ||= filter.is_a?(Azure::Core::Http::RetryPolicy) 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'] = '2.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 if body if IO === body headers['Content-Length'] = body.size.to_s headers['Content-MD5'] = Digest::MD5.file(body.path).base64digest unless headers['Content-MD5'] elsif StringIO === body 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 else headers['Content-Length'] = body.bytesize.to_s headers['Content-MD5'] = Base64.strict_encode64(Digest::MD5.digest(body)) unless headers['Content-MD5'] end else headers['Content-Length'] = '0' end end end end end end