Skip to content

Instantly share code, notes, and snippets.

@ctweney
Created October 30, 2014 20:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ctweney/584ea5f6fd869faf5469 to your computer and use it in GitHub Desktop.
Save ctweney/584ea5f6fd869faf5469 to your computer and use it in GitHub Desktop.
Batch API calls don't observe auto_refresh_token setting
require 'rubygems'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/file_storage'
require 'sinatra'
require 'logger'
enable :sessions
CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json"
def logger; settings.logger end
def api_client; settings.api_client; end
def calendar_api; settings.calendar; end
def user_credentials
# Build a per-request oauth credential based on token stored in session
# which allows us to use a shared API client.
@authorization ||= (
auth = api_client.authorization.dup
auth.redirect_uri = to('/oauth2callback')
auth.update_token!(session)
auth
)
end
configure do
log_file = File.open('calendar.log', 'a+')
log_file.sync = true
logger = Logger.new(log_file)
logger.level = Logger::DEBUG
client = Google::APIClient.new(
:application_name => 'Ruby Calendar sample',
:application_version => '1.0.0',
:auto_refresh_token => true,
:retries => 3
)
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
if file_storage.authorization.nil?
client_secrets = Google::APIClient::ClientSecrets.load
client.authorization = client_secrets.to_authorization
client.authorization.scope = 'https://www.googleapis.com/auth/calendar'
else
client.authorization = file_storage.authorization
end
# Since we're saving the API definition to the settings, we're only retrieving
# it once (on server start) and saving it between requests.
# If this is still an issue, you could serialize the object and load it on
# subsequent runs.
calendar = client.discovered_api('calendar', 'v3')
set :logger, logger
set :api_client, client
set :calendar, calendar
end
before do
# Ensure user has authorized the app
unless user_credentials.access_token || request.path_info =~ /\A\/oauth2/
redirect to('/oauth2authorize')
end
end
after do
# Serialize the access/refresh token to the session and credential store.
session[:access_token] = user_credentials.access_token
session[:refresh_token] = user_credentials.refresh_token
session[:expires_in] = user_credentials.expires_in
session[:issued_at] = user_credentials.issued_at
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
file_storage.write_credentials(user_credentials)
end
get '/oauth2authorize' do
# Request authorization
redirect user_credentials.authorization_uri.to_s, 303
end
get '/oauth2callback' do
# Exchange token
user_credentials.code = params[:code] if params[:code]
user_credentials.fetch_access_token!
redirect to('/')
end
# single API requests with auto_refresh_token:true will work even when the access token has expired.
get '/single' do
old_token = api_client.authorization.access_token
puts "Token before = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
result = api_client.execute(:api_method => calendar_api.events.list,
:parameters => {'calendarId' => 'primary'},
:authorization => user_credentials)
puts "Token changed? #{old_token != api_client.authorization.access_token}"
puts "Token after = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
[result.status, {'Content-Type' => 'application/json'}, result.data.to_json]
end
# batched API requests with auto_refresh_token:true will NOT work when the access token has expired.
get '/batch' do
old_token = api_client.authorization.access_token
puts "Token before = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
results_json = []
batch = Google::APIClient::BatchRequest.new do |result|
puts "Batch response status: #{result.response.status}"
puts "Token currently = #{api_client.authorization.access_token}, changed? #{old_token != api_client.authorization.access_token}"
results_json << result.data.to_json
end
batch.add(:api_method => calendar_api.events.get,
:parameters => {'eventId' => 'jfu2jg6495nbbp43q3832ln65k', 'calendarId' => 'primary'},
:authorization => user_credentials)
batch.add(:api_method => calendar_api.events.get,
:parameters => {'eventId' => 'dpdni6vbvbnopr6dbu8r7iq7vg', 'calendarId' => 'primary'},
:authorization => user_credentials)
batch.add(:api_method => calendar_api.events.get,
:parameters => {'eventId' => 'abcdef', 'calendarId' => 'primary'},
:authorization => user_credentials)
api_client.execute batch
puts "Token changed? #{old_token != api_client.authorization.access_token}"
puts "Token after = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
[200, {'Content-Type' => 'application/json'}, results_json]
end
# explicitly updating the token with fetch_access_token! makes batched API requests work correctly, even with an expired token.
get '/batch_refreshing' do
old_token = api_client.authorization.access_token
puts "Token before = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
# now refresh the token
api_client.authorization.fetch_access_token!
puts "Token after refreshing, before batch = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
results_json = []
batch = Google::APIClient::BatchRequest.new do |result|
puts "Batch response status: #{result.response.status}"
puts "Token currently = #{api_client.authorization.access_token}, changed? #{old_token != api_client.authorization.access_token}"
results_json << result.data.to_json
end
batch.add(:api_method => calendar_api.events.get,
:parameters => {'eventId' => 'jfu2jg6495nbbp43q3832ln65k', 'calendarId' => 'primary'},
:authorization => user_credentials)
batch.add(:api_method => calendar_api.events.get,
:parameters => {'eventId' => 'dpdni6vbvbnopr6dbu8r7iq7vg', 'calendarId' => 'primary'},
:authorization => user_credentials)
batch.add(:api_method => calendar_api.events.get,
:parameters => {'eventId' => 'abcdef', 'calendarId' => 'primary'},
:authorization => user_credentials)
api_client.execute batch
puts "Token changed? #{old_token != api_client.authorization.access_token}"
puts "Token after batch = #{api_client.authorization.access_token}; expires_in #{api_client.authorization.expires_in}; issued_at #{api_client.authorization.issued_at}"
[200, {'Content-Type' => 'application/json'}, results_json]
end
@sqrrrl
Copy link

sqrrrl commented Dec 8, 2014

FWIW, if you're using the same credentials for each individual request you can just specify the credentials either at the connection (api_client.authorization) or as an option to api_client.execute. In either of those two cases refresh will work as expected.

Handling refresh/retries for the individual requests can get very complicated quickly...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment