require "concurrent/atomics" require "json" require "uri" module LaunchDarkly # @private class UnexpectedResponseError < StandardError def initialize(status) @status = status super("HTTP error #{status}") end def status @status end end # @private class Requestor CacheEntry = Struct.new(:etag, :body) def initialize(sdk_key, config) @sdk_key = sdk_key @config = config @client = Util.new_http_client(@config.base_uri, @config) @cache = @config.cache_store end def request_flag(key) make_request("/sdk/latest-flags/" + key) end def request_segment(key) make_request("/sdk/latest-segments/" + key) end def request_all_data() make_request("/sdk/latest-all") end def stop begin @client.finish rescue end end private def make_request(path) @client.start if !@client.started? uri = URI(@config.base_uri + path) req = Net::HTTP::Get.new(uri) req["Authorization"] = @sdk_key req["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION req["Connection"] = "keep-alive" cached = @cache.read(uri) if !cached.nil? req["If-None-Match"] = cached.etag end res = @client.request(req) status = res.code.to_i @config.logger.debug { "[LDClient] Got response from uri: #{uri}\n\tstatus code: #{status}\n\theaders: #{res.to_hash}\n\tbody: #{res.body}" } if status == 304 && !cached.nil? body = cached.body else @cache.delete(uri) if status < 200 || status >= 300 raise UnexpectedResponseError.new(status) end body = fix_encoding(res.body, res["content-type"]) etag = res["etag"] @cache.write(uri, CacheEntry.new(etag, body)) if !etag.nil? end JSON.parse(body, symbolize_names: true) end def fix_encoding(body, content_type) return body if content_type.nil? media_type, charset = parse_content_type(content_type) return body if charset.nil? body.force_encoding(Encoding::find(charset)).encode(Encoding::UTF_8) end def parse_content_type(value) return [nil, nil] if value.nil? || value == '' parts = value.split(/; */) return [value, nil] if parts.count < 2 charset = nil parts.each do |part| fields = part.split('=') if fields.count >= 2 && fields[0] == 'charset' charset = fields[1] break end end return [parts[0], charset] end end end