azure-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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