class BlizzardApi::Request

Simplifies the requests to Blizzard APIS

Constants

BASE_URLS

Common endpoints

CACHE_DAY

One day cache

CACHE_HOUR

One hour cache

CACHE_TRIMESTER

Three (commercial) months cache

Attributes

mode[RW]

@!attribute mode

@return [:regular, :extended]
region[RW]

@!attribute region

@return [String] Api region

Public Class Methods

new(region = nil, mode = :regular) click to toggle source

@!macro regions

# File lib/blizzard_api/request.rb, line 66
def initialize(region = nil, mode = :regular)
  self.region = region || BlizzardApi.region
  @redis = Redis.new(host: BlizzardApi.redis_host, port: BlizzardApi.redis_port) if BlizzardApi.use_cache
  # Use the shared access_token, or create one if it doesn't exists. This avoids unnecessary calls to create tokens.
  @access_token = BlizzardApi.access_token || create_access_token
  # Mode
  @mode = mode
end

Protected Instance Methods

api_request(uri, **query_string) click to toggle source
# File lib/blizzard_api/request.rb, line 154
def api_request(uri, **query_string)
  # List of request options
  options_key = %i[ignore_cache ttl format access_token namespace classic classic1x headers since]

  # Separates request options from api fields and options. Any user-defined option will be treated as api field.
  options = query_string.select { |k, _v| query_string.delete(k) || true if options_key.include? k }

  # Namespace
  query_string[:namespace] = endpoint_namespace(options) if options.include? :namespace

  # In case uri already have query string parameters joins them with &
  if query_string.size.positive?
    query_string = URI.encode_www_form(query_string, false)
    uri = uri.include?('?') ? "#{uri}&#{query_string}" : "#{uri}?#{query_string}"
  end

  request uri, **options
end
base_url(scope) click to toggle source
# File lib/blizzard_api/request.rb, line 82
def base_url(scope)
  raise ArgumentError, 'Invalid scope' unless BASE_URLS.include? scope

  format BASE_URLS[scope], region, @game
end
create_access_token() click to toggle source
# File lib/blizzard_api/request.rb, line 120
def create_access_token
  uri = URI.parse("https://#{BlizzardApi.region}.battle.net/oauth/token")

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.path)
  request.basic_auth(BlizzardApi.app_id, BlizzardApi.app_secret)
  request['Content-Type'] = 'application/x-www-form-urlencoded'
  request.set_form_data grant_type: 'client_credentials'

  response = http.request(request)
  BlizzardApi.access_token = JSON.parse(response.body)['access_token']
end
endpoint_namespace(options) click to toggle source

Returns a valid namespace string for consuming the api endpoints

@param [Hash] options A hash containing the namespace key

# File lib/blizzard_api/request.rb, line 106
def endpoint_namespace(options)
  version = endpoint_version(options)
  case options[:namespace]
  when :dynamic
    "dynamic-#{version}#{region}"
  when :static
    "static-#{version}#{region}"
  when :profile
    "profile-#{region}"
  else
    raise ArgumentError, 'Invalid namespace scope'
  end
end
endpoint_version(options) click to toggle source

Returns a valid version namespace

@param [Hash] options A hash containing a valid namespace key

# File lib/blizzard_api/request.rb, line 92
def endpoint_version(options)
  if options.key? :classic
    'classic-'
  elsif options.key? :classic1x
    'classic1x-'
  else
    ''
  end
end
request(url, **options) click to toggle source
# File lib/blizzard_api/request.rb, line 135
def request(url, **options)
  # Creates the whole url for request
  parsed_url = URI.parse(url)

  data = using_cache?(options) ? find_in_cache(parsed_url.to_s) : nil

  # If data was found that means cache is enabled and valid
  return JSON.parse(data, symbolize_names: true) if data

  response = consume_api parsed_url, **options

  save_in_cache parsed_url.to_s, response.body, options[:ttl] || CACHE_DAY if using_cache? options

  response_data = response.code.to_i.eql?(304) ? nil : JSON.parse(response.body, symbolize_names: true)
  return [response, response_data] if mode.eql? :extended

  response_data
end

Private Instance Methods

add_headers(request, options) click to toggle source
# File lib/blizzard_api/request.rb, line 199
def add_headers(request, options)
  # Blizzard API documentation states the preferred way to send the access_token is using Bearer token on header
  request['Authorization'] = "Bearer #{options.fetch(:access_token, @access_token)}"
  # Format If-modified-since option
  request['If-Modified-Since'] = options[:since].httpdate if options.key? :since
  options[:headers]&.each { |header, content| request[header] = content }
end
consume_api(url, **options) click to toggle source
# File lib/blizzard_api/request.rb, line 183
def consume_api(url, **options)
  # Creates a HTTP connection and request to ensure thread safety
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  request = Net::HTTP::Get.new(url)

  add_headers request, options

  # Executes the request
  http.request(request).tap do |response|
    if mode.eql?(:regular) && ![200, 304].include?(response.code.to_i)
      raise BlizzardApi::ApiException.new "Request failed with code '#{response.code}' details: #{response.to_hash}", response.code.to_i
    end
  end
end
find_in_cache(resource_url) click to toggle source
# File lib/blizzard_api/request.rb, line 211
def find_in_cache(resource_url)
  return false unless BlizzardApi.use_cache

  @redis.get resource_url if @redis.exists? resource_url
end
save_in_cache(resource_url, data, ttl) click to toggle source
# File lib/blizzard_api/request.rb, line 207
def save_in_cache(resource_url, data, ttl)
  @redis.setex resource_url, ttl, data if BlizzardApi.use_cache
end
using_cache?(options) click to toggle source

@param options [Hash] Request options

# File lib/blizzard_api/request.rb, line 177
def using_cache?(options)
  return false if mode.eql?(:extended) || options.key?(:since)

  !options.fetch(:ignore_cache, false)
end