azure-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ require 'openssl'
16
+ require 'base64'
17
+
18
+ module Azure
19
+ module Core
20
+ module Auth
21
+ # Utility class to sign strings with HMAC-256 and then encode the
22
+ # signed string using Base64.
23
+ class Signer
24
+ # The access key for the account
25
+ attr :access_key
26
+
27
+ # Initialize the Signer.
28
+ #
29
+ # @param access_key [String] The access_key encoded in Base64.
30
+ def initialize(access_key)
31
+ if access_key.nil?
32
+ raise ArgumentError, 'Signing key must be provided'
33
+ end
34
+
35
+ @access_key = Base64.strict_decode64(access_key)
36
+ end
37
+
38
+ # Generate an HMAC signature.
39
+ #
40
+ # @param body [String] The string to sign.
41
+ #
42
+ # @return [String] a Base64 String signed with HMAC.
43
+ def sign(body)
44
+ signed = OpenSSL::HMAC.digest('sha256', access_key, body)
45
+ Base64.strict_encode64(signed)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+
16
+ module Azure
17
+ module Core
18
+ module Default
19
+ # Default User Agent header string
20
+ USER_AGENT = "Azure-Core/#{Azure::Core::Version}".freeze
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ module Azure
16
+ module Core
17
+ # Superclass for errors generated from this library, so people can
18
+ # just rescue this for generic error handling
19
+ class Error < StandardError;end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ require 'azure/core/service'
16
+
17
+ module Azure
18
+ module Core
19
+ # A base class for Service implementations
20
+ class FilteredService < Service
21
+
22
+ # Create a new instance of the FilteredService
23
+ #
24
+ # @param host [String] The hostname. (optional, Default empty)
25
+ # @param options [Hash] options including {:client} (optional, Default {})
26
+ def initialize(host='', options={})
27
+ super
28
+ @filters = []
29
+ end
30
+
31
+ attr_accessor :filters
32
+
33
+ def call(method, uri, body=nil, headers=nil)
34
+ super(method, uri, body, headers) do |request|
35
+ filters.each { |filter| request.with_filter filter } if filters
36
+ end
37
+ end
38
+
39
+ def with_filter(filter=nil, &block)
40
+ filter = filter || block
41
+ filters.push filter if filter
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ require "azure/core/http/http_filter"
16
+
17
+ module Azure
18
+ module Core
19
+ module Http
20
+ # A HttpFilter implementation that displays information about the request and response for debugging
21
+ class DebugFilter < HttpFilter
22
+ def call(req, _next)
23
+ puts "--REQUEST-BEGIN---------------------------"
24
+ puts "method:", req.method, "uri:", req.uri, "headers:", req.headers, "body:", req.body
25
+ puts "--REQUEST-END---------------------------"
26
+
27
+ r = _next.call
28
+ puts "--RESPONSE-BEGIN---------------------------"
29
+ puts "status_code:", r.status_code, "headers:", r.headers, "body:", r.body
30
+ puts "--RESPONSE-END---------------------------"
31
+ r
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,87 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ require 'azure/core/error'
16
+ require 'nokogiri'
17
+
18
+ module Azure
19
+ module Core
20
+ module Http
21
+ # Public: Class for handling all HTTP response errors
22
+ class HTTPError < Azure::Core::Error
23
+
24
+ attr :uri
25
+
26
+ # Public: The HTTP status code of this error
27
+ #
28
+ # Returns a Fixnum
29
+ attr :status_code
30
+
31
+ # Public: The type of error
32
+ #
33
+ # http://msdn.microsoft.com/en-us/library/azure/dd179357
34
+ #
35
+ # Returns a String
36
+ attr :type
37
+
38
+ # Public: Description of the error
39
+ #
40
+ # Returns a String
41
+ attr :description
42
+
43
+ # Public: Detail of the error
44
+ #
45
+ # Returns a String
46
+ attr :detail
47
+
48
+ # Public: Initialize an error
49
+ #
50
+ # http_response - An Azure::Core::HttpResponse
51
+ def initialize(http_response)
52
+ @http_response = http_response
53
+ @uri = http_response.uri
54
+ @status_code = http_response.status_code
55
+ parse_response
56
+ super("#{type} (#{status_code}): #{description}")
57
+ end
58
+
59
+ # Extract the relevant information from the response's body. If the response
60
+ # body is not an XML, we return an 'Unknown' error with the entire body as
61
+ # the description
62
+ #
63
+ # Returns nothing
64
+ def parse_response
65
+ if @http_response.body && @http_response.body.include?('<')
66
+
67
+ document = Nokogiri.Slop(@http_response.body)
68
+
69
+ @type = document.css('code').first.text if document.css('code').any?
70
+ @type = document.css('Code').first.text if document.css('Code').any?
71
+ @description = document.css('message').first.text if document.css('message').any?
72
+ @description = document.css('Message').first.text if document.css('Message').any?
73
+
74
+ # service bus uses detail instead of message
75
+ @detail = document.css('detail').first.text if document.css('detail').any?
76
+ @detail = document.css('Detail').first.text if document.css('Detail').any?
77
+ else
78
+ @type = 'Unknown'
79
+ if @http_response.body
80
+ @description = "#{@http_response.body.strip}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,53 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ module Azure
16
+ module Core
17
+ module Http
18
+ # A filter which can modify the HTTP pipeline both before and
19
+ # after requests/responses. Multiple filters can be nested in a
20
+ # "Russian Doll" model to create a compound HTTP pipeline
21
+ class HttpFilter
22
+
23
+ # Initialize a HttpFilter
24
+ #
25
+ # &block - An inline block which implements the filter.
26
+ #
27
+ # The inline block should take parameters |request, _next| where
28
+ # request is a HttpRequest and _next is an object that implements
29
+ # a method .call which returns an HttpResponse. The block passed
30
+ # to the constructor should also return HttpResponse, either as
31
+ # the result of calling _next.call or by customized logic.
32
+ #
33
+ def initialize(&block)
34
+ @block = block
35
+ end
36
+
37
+ # Executes the filter
38
+ #
39
+ # request - HttpRequest. The request
40
+ # _next - An object that implements .call (no params)
41
+ #
42
+ # NOTE: _next is a either a subsequent HttpFilter wrapped in a
43
+ # closure, or the HttpRequest object's call method. Either way,
44
+ # it must have it's .call method executed within each filter to
45
+ # complete the pipeline. _next.call should return an HttpResponse
46
+ # and so should this Filter.
47
+ def call(request, _next)
48
+ @block ? @block.call(request, _next) : _next.call
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,175 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #--------------------------------------------------------------------------
15
+ require 'digest/md5'
16
+ require 'base64'
17
+ require 'net/http'
18
+ require 'time'
19
+
20
+ require 'azure/core/version'
21
+ require 'azure/core/http/http_response'
22
+ require 'azure/core/default'
23
+ require 'azure/http_response_helper'
24
+
25
+ module Azure
26
+ module Core
27
+ module Http
28
+ # Represents a HTTP request can perform synchronous queries to a
29
+ # HTTP server, returning a HttpResponse
30
+ class HttpRequest
31
+ include Azure::HttpResponseHelper
32
+ alias_method :_method, :method
33
+
34
+ # The HTTP method to use (:get, :post, :put, :delete, etc...)
35
+ attr_accessor :method
36
+
37
+ # The URI of the HTTP endpoint to query
38
+ attr_accessor :uri
39
+
40
+ # The header values as a Hash
41
+ attr_accessor :headers
42
+
43
+ # The body of the request (IO or String)
44
+ attr_accessor :body
45
+
46
+ # Azure client which contains configuration context and http agents
47
+ # @return [Azure::Client]
48
+ attr_accessor :client
49
+
50
+ # Public: Create the HttpRequest
51
+ #
52
+ # @param method [Symbol] The HTTP method to use (:get, :post, :put, :del, etc...)
53
+ # @param uri [URI] The URI of the HTTP endpoint to query
54
+ # @param options_or_body [Hash|IO|String] The request options including {:client, :body} or raw body only
55
+ def initialize(method, uri, options_or_body = {})
56
+ options ||= unless options_or_body.is_a?(Hash)
57
+ {body: options_or_body}
58
+ end || options_or_body || {}
59
+
60
+ @method = method
61
+ @uri = if uri.is_a?(String)
62
+ URI.parse(uri)
63
+ else
64
+ uri
65
+ end
66
+
67
+ @client = options[:client] || Azure
68
+
69
+ self.headers = default_headers(options[:current_time] || Time.now.httpdate).merge(options[:headers] || {})
70
+ self.body = options[:body]
71
+ end
72
+
73
+ # Public: Applies a HttpFilter to the HTTP Pipeline
74
+ #
75
+ # filter - Any object that responds to .call(req, _next) and
76
+ # returns a HttpResponse eg. HttpFilter, Proc,
77
+ # lambda, etc. (optional)
78
+ #
79
+ # &block - An inline block may be used instead of a filter
80
+ #
81
+ # example:
82
+ #
83
+ # request.with_filter do |req, _next|
84
+ # _next.call
85
+ # end
86
+ #
87
+ # NOTE:
88
+ #
89
+ # The code block provided must call _next or the filter pipeline
90
+ # will not complete and the HTTP request will never execute
91
+ #
92
+ def with_filter(filter=nil, &block)
93
+ filter = filter || block
94
+ if filter
95
+ old_impl = self._method(:call)
96
+
97
+ # support 1.8.7 (define_singleton_method doesn't exist until 1.9.1)
98
+ new_impl = Proc.new do
99
+ filter.call(self, old_impl)
100
+ end
101
+ k = class << self;
102
+ self;
103
+ end
104
+ if k.method_defined? :define_singleton_method
105
+ self.define_singleton_method(:call, new_impl)
106
+ else
107
+ k.send(:define_method, :call, new_impl)
108
+ end
109
+ end
110
+ end
111
+
112
+ # Build a default headers Hash
113
+ def default_headers(current_time)
114
+ {}.tap do |def_headers|
115
+ def_headers['User-Agent'] = Azure::Core::Default::USER_AGENT
116
+ def_headers['x-ms-date'] = current_time
117
+ def_headers['x-ms-version'] = '2014-02-14'
118
+ def_headers['DataServiceVersion'] = '1.0;NetFx'
119
+ def_headers['MaxDataServiceVersion'] = '2.0;NetFx'
120
+ def_headers['Content-Type'] = 'application/atom+xml; charset=utf-8'
121
+ end
122
+ end
123
+
124
+ def http_setup
125
+ http = @client.agents(uri)
126
+
127
+ unless headers.nil?
128
+ keep_alive = headers['Keep-Alive'] || headers['keep-alive']
129
+ http.read_timeout = keep_alive.split('=').last.to_i unless keep_alive.nil?
130
+ end
131
+
132
+ http
133
+ end
134
+
135
+ def body=(body)
136
+ @body = body
137
+ apply_body_headers
138
+ end
139
+
140
+ # Sends request to HTTP server and returns a HttpResponse
141
+ #
142
+ # @return [HttpResponse]
143
+ def call
144
+ conn = http_setup
145
+ res = conn.run_request(method.to_sym, uri, nil, nil) do |req|
146
+ req.body = body if body
147
+ req.headers = headers if headers
148
+ end
149
+
150
+ response = HttpResponse.new(res)
151
+ response.uri = uri
152
+ raise response.error unless response.success?
153
+ response
154
+ end
155
+
156
+ private
157
+
158
+ def apply_body_headers
159
+ if body
160
+ if IO === body
161
+ headers['Content-Length'] = body.size.to_s
162
+ headers['Content-MD5'] = Digest::MD5.file(body.path).base64digest unless headers['Content-MD5']
163
+ else
164
+ headers['Content-Length'] = body.bytesize.to_s
165
+ headers['Content-MD5'] = Base64.strict_encode64(Digest::MD5.digest(body)) unless headers['Content-MD5']
166
+ end
167
+ else
168
+ headers['Content-Length'] = '0'
169
+ end
170
+ end
171
+
172
+ end
173
+ end
174
+ end
175
+ end