'Makes a JSON request to GCM servers
:param kwargs: dict mapping of key-value pairs of parameters
:return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc'

def json_request(self, **kwargs):

    if ('registration_ids' not in kwargs):
        raise GCMMissingRegistrationException('Missing    registration_ids')
    elif (not kwargs['registration_ids']):
        raise GCMMissingRegistrationException('Empty    registration_ids')
    args = dict(**kwargs)
    retries = args.pop('retries', 5)
    session = args.pop('session', None)
    payload = self.construct_payload(**args)
    registration_ids = args['registration_ids']
    backoff = self.BACKOFF_INITIAL_DELAY
    info = None
    has_error = False
    for attempt in range(retries):
        try:
            response = self.make_request(payload, is_json=True, session=session)
            info = self.handle_json_response(response, registration_ids)
            unsent_reg_ids = self.extract_unsent_reg_ids(info)
            has_error = False
        except GCMUnavailableException:
            unsent_reg_ids = registration_ids
            has_error = True
        if unsent_reg_ids:
            registration_ids = unsent_reg_ids
            args['registration_ids'] = registration_ids
            payload = self.construct_payload(**args)
            if self.retry_after:
                GCM.log('[json_request    -    Attempt    #{0}]    Retry-After    ~>    Sleeping    for    {1}'.format(attempt, self.retry_after))
                time.sleep(self.retry_after)
                self.retry_after = None
            else:
                sleep_time = ((backoff / 2) + random.randrange(backoff))
                nap_time = (float(sleep_time) / 1000)
                GCM.log('[json_request    -    Attempt    #{0}]    Backoff    ~>    Sleeping    for    {1}'.format(attempt, nap_time))
                time.sleep(nap_time)
                if ((2 * backoff) < self.MAX_BACKOFF_DELAY):
                    backoff *= 2
        else:
            break
    if has_error:
        raise IOError(('Could    not    make    request    after    %d    attempts' % retries))
    return info
