google-api-client 0.6.4 → 0.7.0.rc2

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.
@@ -14,7 +14,6 @@
14
14
 
15
15
 
16
16
  require 'faraday'
17
- require 'faraday/utils'
18
17
  require 'multi_json'
19
18
  require 'compat/multi_json'
20
19
  require 'stringio'
@@ -30,6 +29,8 @@ require 'google/api_client/result'
30
29
  require 'google/api_client/media'
31
30
  require 'google/api_client/service_account'
32
31
  require 'google/api_client/batch'
32
+ require 'google/api_client/gzip'
33
+ require 'google/api_client/client_secrets'
33
34
  require 'google/api_client/railtie' if defined?(Rails::Railtie)
34
35
 
35
36
  module Google
@@ -70,6 +71,9 @@ module Google
70
71
  # The port number used by the client. This rarely needs to be changed.
71
72
  # @option options [String] :discovery_path ("/discovery/v1")
72
73
  # The discovery base path. This rarely needs to be changed.
74
+ # @option options [String] :ca_file
75
+ # Optional set of root certificates to use when validating SSL connections.
76
+ # By default, a bundled set of trusted roots will be used.
73
77
  def initialize(options={})
74
78
  logger.debug { "#{self.class} - Initializing client with options #{options}" }
75
79
 
@@ -94,8 +98,7 @@ module Google
94
98
  end
95
99
  self.user_agent = options[:user_agent] || (
96
100
  "#{application_string} " +
97
- "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} " +
98
- ENV::OS_VERSION
101
+ "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION} (gzip)"
99
102
  ).strip
100
103
  # The writer method understands a few Symbols and will generate useful
101
104
  # default authentication mechanisms.
@@ -107,7 +110,14 @@ module Google
107
110
  @discovery_uris = {}
108
111
  @discovery_documents = {}
109
112
  @discovered_apis = {}
110
-
113
+ ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__)
114
+ self.connection = Faraday.new do |faraday|
115
+ faraday.response :gzip
116
+ faraday.options.params_encoder = Faraday::FlatParamsEncoder
117
+ faraday.ssl.ca_file = ca_file
118
+ faraday.ssl.verify = true
119
+ faraday.adapter Faraday.default_adapter
120
+ end
111
121
  return self
112
122
  end
113
123
 
@@ -167,6 +177,12 @@ module Google
167
177
  return @authorization
168
178
  end
169
179
 
180
+ ##
181
+ # Default Faraday/HTTP connection.
182
+ #
183
+ # @return [Faraday::Connection]
184
+ attr_accessor :connection
185
+
170
186
  ##
171
187
  # The setting that controls whether or not the api client attempts to
172
188
  # refresh authorization when a 401 is hit in #execute.
@@ -515,6 +531,8 @@ module Google
515
531
  # - (TrueClass, FalseClass) :authenticated (default: true) -
516
532
  # `true` if the request must be signed or somehow
517
533
  # authenticated, `false` otherwise.
534
+ # - (TrueClass, FalseClass) :gzip (default: true) -
535
+ # `true` if gzip enabled, `false` otherwise.
518
536
  #
519
537
  # @return [Google::APIClient::Result] The result from the API, nil if batch.
520
538
  #
@@ -530,10 +548,9 @@ module Google
530
548
  #
531
549
  # @see Google::APIClient#generate_request
532
550
  def execute(*params)
533
- if params.last.kind_of?(Google::APIClient::Request) &&
534
- params.size == 1
535
- request = params.pop
536
- options = {}
551
+ if params.first.kind_of?(Google::APIClient::Request)
552
+ request = params.shift
553
+ options = params.shift || {}
537
554
  else
538
555
  # This block of code allows us to accept multiple parameter passing
539
556
  # styles, and maintaining some backwards compatibility.
@@ -554,10 +571,11 @@ module Google
554
571
  end
555
572
 
556
573
  request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil?
574
+ request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false
557
575
  request.parameters['key'] ||= self.key unless self.key.nil?
558
576
  request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?
559
577
 
560
- connection = options[:connection] || Faraday.default_connection
578
+ connection = options[:connection] || self.connection
561
579
  request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
562
580
 
563
581
  result = request.send(connection)
@@ -565,7 +583,7 @@ module Google
565
583
  begin
566
584
  logger.debug("Attempting refresh of access token & retry of request")
567
585
  request.authorization.fetch_access_token!
568
- result = request.send(connection)
586
+ result = request.send(connection, true)
569
587
  rescue Signet::AuthorizationError
570
588
  # Ignore since we want the original error
571
589
  end
@@ -0,0 +1,28 @@
1
+ # Copyright 2013 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
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 'faraday'
16
+ require 'signet/oauth_2/client'
17
+
18
+ module Google
19
+ class APIClient
20
+ class ComputeServiceAccount < Signet::OAuth2::Client
21
+ def fetch_access_token(options={})
22
+ connection = options[:connection] || Faraday.default_connection
23
+ response = connection.get 'http://metadata/computeMetadata/v1beta1/instance/service-accounts/default/token'
24
+ Signet::OAuth2.parse_json_credentials(response.body)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,87 @@
1
+ # Copyright 2013 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
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 'json'
16
+ require 'signet/oauth_2/client'
17
+
18
+ module Google
19
+ class APIClient
20
+ ##
21
+ # Represents cached OAuth 2 tokens stored on local disk in a
22
+ # JSON serialized file. Meant to resemble the serialized format
23
+ # http://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.file.Storage-class.html
24
+ #
25
+ class FileStorage
26
+ # @return [String] Path to the credentials file.
27
+ attr_accessor :path
28
+
29
+ # @return [Signet::OAuth2::Client] Path to the credentials file.
30
+ attr_reader :authorization
31
+
32
+ ##
33
+ # Initializes the FileStorage object.
34
+ #
35
+ # @param [String] path
36
+ # Path to the credentials file.
37
+ def initialize(path)
38
+ @path = path
39
+ self.load_credentials
40
+ end
41
+
42
+ ##
43
+ # Attempt to read in credentials from the specified file.
44
+ def load_credentials
45
+ if File.exist? self.path
46
+ File.open(self.path, 'r') do |file|
47
+ cached_credentials = JSON.load(file)
48
+ @authorization = Signet::OAuth2::Client.new(cached_credentials)
49
+ @authorization.issued_at = Time.at(cached_credentials['issued_at'])
50
+ if @authorization.expired?
51
+ @authorization.fetch_access_token!
52
+ self.write_credentials
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Write the credentials to the specified file.
60
+ #
61
+ # @param [Signet::OAuth2::Client] authorization
62
+ # Optional authorization instance. If not provided, the authorization
63
+ # already associated with this instance will be written.
64
+ def write_credentials(authorization=nil)
65
+ @authorization = authorization unless authorization.nil?
66
+
67
+ unless @authorization.refresh_token.nil?
68
+ hash = {}
69
+ %w'access_token
70
+ authorization_uri
71
+ client_id
72
+ client_secret
73
+ expires_in
74
+ refresh_token
75
+ token_credential_uri'.each do |var|
76
+ hash[var] = @authorization.instance_variable_get("@#{var}")
77
+ end
78
+ hash['issued_at'] = @authorization.issued_at.to_i
79
+
80
+ File.open(self.path, 'w', 0600) do |file|
81
+ file.write(hash.to_json)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -77,9 +77,13 @@ module Google
77
77
  ##
78
78
  # Request authorization. Opens a browser and waits for response.
79
79
  #
80
+ # @param [Google::APIClient::FileStorage] storage
81
+ # Optional object that responds to :write_credentials, used to serialize
82
+ # the OAuth 2 credentials after completing the flow.
83
+ #
80
84
  # @return [Signet::OAuth2::Client]
81
85
  # Authorization instance, nil if user cancelled.
82
- def authorize
86
+ def authorize(storage=nil)
83
87
  auth = @authorization
84
88
 
85
89
  server = WEBrick::HTTPServer.new(
@@ -103,6 +107,9 @@ module Google
103
107
  Launchy.open(auth.authorization_uri.to_s)
104
108
  server.start
105
109
  if @authorization.access_token
110
+ if storage.respond_to?(:write_credentials)
111
+ storage.write_credentials(@authorization)
112
+ end
106
113
  return @authorization
107
114
  else
108
115
  return nil
@@ -34,17 +34,6 @@ module Google
34
34
  # client.authorization.fetch_access_token!
35
35
  # client.execute(...)
36
36
  #
37
- # @example Deprecated version
38
- #
39
- # client = Google::APIClient.new
40
- # key = Google::APIClient::PKCS12.load_key('client.p12', 'notasecret')
41
- # service_account = Google::APIClient::JWTAsserter.new(
42
- # '123456-abcdef@developer.gserviceaccount.com',
43
- # 'https://www.googleapis.com/auth/prediction',
44
- # key)
45
- # client.authorization = service_account.authorize
46
- # client.execute(...)
47
- #
48
37
  # @deprecated
49
38
  # Service accounts are now supported directly in Signet
50
39
  # @see https://developers.google.com/accounts/docs/OAuth2ServiceAccount
@@ -146,7 +146,7 @@ module Google
146
146
  end
147
147
 
148
148
  def to_authorization
149
- gem 'signet', '~> 0.4.0'
149
+ gem 'signet', '>= 0.4.0'
150
150
  require 'signet/oauth_2/client'
151
151
  # NOTE: Do not rely on this default value, as it may change
152
152
  new_authorization = Signet::OAuth2::Client.new
@@ -187,6 +187,7 @@ module Google
187
187
  # @return [Addressable::URI] The URI after expansion.
188
188
  def generate_uri(parameters={})
189
189
  parameters = self.normalize_parameters(parameters)
190
+
190
191
  self.validate_parameters(parameters)
191
192
  template_variables = self.uri_template.variables
192
193
  upload_type = parameters.assoc('uploadType') || parameters.assoc('upload_type')
@@ -0,0 +1,28 @@
1
+ require 'faraday'
2
+ require 'zlib'
3
+
4
+ module Google
5
+ class APIClient
6
+ class Gzip < Faraday::Response::Middleware
7
+ include Google::APIClient::Logging
8
+
9
+ def on_complete(env)
10
+ encoding = env[:response_headers]['content-encoding'].to_s.downcase
11
+ case encoding
12
+ when 'gzip'
13
+ logger.debug { "Decompressing gzip encoded response (#{env[:body].length} bytes)" }
14
+ env[:body] = Zlib::GzipReader.new(StringIO.new(env[:body])).read
15
+ env[:response_headers].delete('content-encoding')
16
+ logger.debug { "Decompressed (#{env[:body].length} bytes)" }
17
+ when 'deflate'
18
+ logger.debug{ "Decompressing deflate encoded response (#{env[:body].length} bytes)" }
19
+ env[:body] = Zlib::Inflate.inflate(env[:body])
20
+ env[:response_headers].delete('content-encoding')
21
+ logger.debug { "Decompressed (#{env[:body].length} bytes)" }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Faraday::Response.register_middleware :gzip => Google::APIClient::Gzip
@@ -21,7 +21,11 @@ module Google
21
21
  # @see Faraday::UploadIO
22
22
  # @example
23
23
  # media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
24
- class UploadIO < Faraday::UploadIO
24
+ class UploadIO < Faraday::UploadIO
25
+
26
+ # @return [Fixnum] Size of chunks to upload. Default is nil, meaning upload the entire file in a single request
27
+ attr_accessor :chunk_size
28
+
25
29
  ##
26
30
  # Get the length of the stream
27
31
  #
@@ -32,6 +36,77 @@ module Google
32
36
  end
33
37
  end
34
38
 
39
+ ##
40
+ # Wraps an input stream and limits data to a given range
41
+ #
42
+ # @example
43
+ # chunk = Google::APIClient::RangedIO.new(io, 0, 1000)
44
+ class RangedIO
45
+ ##
46
+ # Bind an input stream to a specific range.
47
+ #
48
+ # @param [IO] io
49
+ # Source input stream
50
+ # @param [Fixnum] offset
51
+ # Starting offset of the range
52
+ # @param [Fixnum] length
53
+ # Length of range
54
+ def initialize(io, offset, length)
55
+ @io = io
56
+ @offset = offset
57
+ @length = length
58
+ self.rewind
59
+ end
60
+
61
+ ##
62
+ # @see IO#read
63
+ def read(amount = nil, buf = nil)
64
+ buffer = buf || ''
65
+ if amount.nil?
66
+ size = @length - @pos
67
+ done = ''
68
+ elsif amount == 0
69
+ size = 0
70
+ done = ''
71
+ else
72
+ size = [@length - @pos, amount].min
73
+ done = nil
74
+ end
75
+
76
+ if size > 0
77
+ result = @io.read(size)
78
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
79
+ buffer << result if result
80
+ @pos = @pos + size
81
+ end
82
+
83
+ if buffer.length > 0
84
+ buffer
85
+ else
86
+ done
87
+ end
88
+ end
89
+
90
+ ##
91
+ # @see IO#rewind
92
+ def rewind
93
+ self.pos = 0
94
+ end
95
+
96
+ ##
97
+ # @see IO#pos
98
+ def pos
99
+ @pos
100
+ end
101
+
102
+ ##
103
+ # @see IO#pos=
104
+ def pos=(pos)
105
+ @pos = pos
106
+ @io.pos = @offset + pos
107
+ end
108
+ end
109
+
35
110
  ##
36
111
  # Resumable uploader.
37
112
  #
@@ -124,11 +199,11 @@ module Google
124
199
  'Content-Range' => "bytes */#{media.length}" })
125
200
  else
126
201
  start_offset = @offset
127
- self.media.io.pos = start_offset
128
- chunk = self.media.io.read(chunk_size)
129
- content_length = chunk.bytesize
202
+ remaining = self.media.length - start_offset
203
+ chunk_size = self.media.chunk_size || self.chunk_size || self.media.length
204
+ content_length = [remaining, chunk_size].min
205
+ chunk = RangedIO.new(self.media.io, start_offset, content_length)
130
206
  end_offset = start_offset + content_length - 1
131
-
132
207
  self.headers.update({
133
208
  'Content-Length' => "#{content_length}",
134
209
  'Content-Type' => self.media.content_type,
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require 'faraday'
16
- require 'faraday/utils'
16
+ require 'faraday/request/multipart'
17
17
  require 'multi_json'
18
18
  require 'compat/multi_json'
19
19
  require 'addressable/uri'
@@ -71,8 +71,10 @@ module Google
71
71
  # @option options [String, Symbol] :http_method
72
72
  # HTTP method when requesting a URI
73
73
  def initialize(options={})
74
- @parameters = Hash[options[:parameters] || {}]
74
+ @parameters = Faraday::Utils::ParamsHash.new
75
75
  @headers = Faraday::Utils::Headers.new
76
+
77
+ self.parameters.merge!(options[:parameters]) unless options[:parameters].nil?
76
78
  self.headers.merge!(options[:headers]) unless options[:headers].nil?
77
79
  self.api_method = options[:api_method]
78
80
  self.authenticated = options[:authenticated]
@@ -150,10 +152,13 @@ module Google
150
152
  #
151
153
  # @param [Faraday::Connection] connection
152
154
  # the connection to transmit with
155
+ # @param [TrueValue,FalseValue] is_retry
156
+ # True if request has been previous sent
153
157
  #
154
158
  # @return [Google::APIClient::Result]
155
159
  # result of API request
156
- def send(connection)
160
+ def send(connection, is_retry = false)
161
+ self.body.rewind if is_retry && self.body.respond_to?(:rewind)
157
162
  env = self.to_env(connection)
158
163
  logger.debug { "#{self.class} Sending API request #{env[:method]} #{env[:url].to_s} #{env[:request_headers]}" }
159
164
  http_response = connection.app.call(env)
@@ -163,8 +168,8 @@ module Google
163
168
 
164
169
  # Resumamble slightly different than other upload protocols in that it requires at least
165
170
  # 2 requests.
166
- if self.upload_type == 'resumable'
167
- upload = result.resumable_upload
171
+ if result.status == 200 && self.upload_type == 'resumable'
172
+ upload = result.resumable_upload
168
173
  unless upload.complete?
169
174
  logger.debug { "#{self.class} Sending upload body" }
170
175
  result = upload.send(connection)
@@ -314,10 +319,10 @@ module Google
314
319
  # @param [String] boundary
315
320
  # Boundary for separating each part of the message
316
321
  def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY)
317
- env = {
318
- :request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"},
319
- :request => { :boundary => boundary }
320
- }
322
+ env = Faraday::Env.new
323
+ env.request = Faraday::RequestOptions.new
324
+ env.request.boundary = boundary
325
+ env.request_headers = {'Content-Type' => "#{mime_type};boundary=#{boundary}"}
321
326
  multipart = Faraday::Request::Multipart.new
322
327
  self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
323
328
  self.headers.update(env[:request_headers])