Повторная отправка запроса post/put без исходных данных

У меня есть вопрос, который в последнее время ставит меня в тупик. Я создаю простой веб-браузер для взаимодействия с одним веб-сайтом для управления пользователями продукта. Проблема, с которой я сейчас сталкиваюсь, заключается в следующем: пользователь может загрузить новый файл через пользовательский интерфейс с помощью базовой веб-формы. После выбора загрузки запрос завершится ошибкой с кодом 205, а именно:

QNetworkReply::ContentReSendError — запрос нужно было отправить еще раз, но это не удалось, например, из-за того, что данные загрузки не удалось прочитать во второй раз.

Если пользователь попытается загрузить еще раз, файл будет загружен без ошибок. Чтобы исправить это, я хотел бы определить, была ли ошибка, и если да, то повторно отправить запрос.

Моя проблема в том, что у меня нет данных для запроса. Я могу получить сам запрос, но не данные. Я представляю, как мое решение выглядит как вложенное if в этот вопрос о переполнении стека:

if((reply->error() == QNetworkReply::TemporaryNetworkFailureError) || (reply->error() == QNetworkReply::ContentReSendError)) {
     // retry the last request
     _reply = accessManager.put(reply->request(), _currentItem);
}

Однако у этого пользователя есть доступ к _currentItem, потому что он изначально создал запрос в своей программе, а мой создается на лету. Есть ли способ, которым я могу захватить отправляемые данные или кэшировать их? Я надеялся, что QNetworkAccessManager сохранил предыдущие запросы для простой повторной попытки, но я не вижу ничего подобного. Спасибо за любую информацию и помощь!

ИЗМЕНИТЬ

Для уточнения: можно ли в любом случае использовать QNetworkAccessManager для получения как запроса, так и данных для публикации/пути, которые не были определены в программе.


person bmartin    schedule 23.06.2014    source источник


Ответы (2)


Да, есть два способа, но такой возможности как таковой не существует.

  1. Отредактируйте исходный код Qt, чтобы добавить его непосредственно в QNetworkAccessManager, или

  2. Реализуйте его как кэширующий HTTP-прокси, работающий в том же процессе. Реализация прокси может использовать вложенный QNetworkAccessManager для выполнения запросов в реальной сети. Реализации потребуется декодировать входящий запрос, перестроить заголовок QNetworkRequest и передать его вложенному менеджеру вместе с любыми данными запроса. В этот момент вы можете отложить данные и реализовать повторную попытку на уровне прокси.

Вероятно, было бы меньше работы и хлопот для реализации варианта 1, но некоторые проекты иррационально не желают модифицировать Qt, поэтому для таких случаев я выложу вариант 2.

person Kuba hasn't forgotten Monica    schedule 24.06.2014
comment
Ах, это прискорбно. Я буду продолжать искать другое решение. Спасибо за ответ! - person bmartin; 25.06.2014

Используя Python с PyQt, я решил аналогичную проблему, используя декоратор функций, который сохраняет запрос в лямбда-функции и повторно отправляет его в случае сбоя. Возможно, аналогичная структура возможна в C++.

В моей конкретной ситуации приложение должно было работать с системой OAuth2, которая выдавала токены, которые были действительны только в течение одного часа (и не могли быть обновлены; неявное предоставление). Таким образом, может случиться так, что по прошествии этого часа пользователь внезапно получит ответ «Не аутентифицирован» на один из своих запросов, что, конечно, не очень удобно для пользователя. Поэтому я подумал, что перехватлю этот ответ 401, позволю пользователю повторно аутентифицироваться, а затем повторно отправлю последний неудачный запрос.

В моем подклассе QNetworkAccessManager я украсил свои HTTP-функции (get, put, post и т. д.) функцией с именем require_internet_connection, поэтому моя функция get, например, выглядит так:

 @check_network_accessibility
 def get(self, url, callback, *args, **kwargs):
    """ Perform a HTTP GET request """
    ...

А декоратор выглядел так:

def check_network_accessibility(func):
    """ Decorator function, not to be called directly.
    Buffers the network request so that it can be sent again if it fails the first time, due to an invalidated 
    OAuth2 token. In this case the user will be presented with the login 
    screen again. If the *same* user successfully logs in again, the request 
    will be resent. """

    @wraps(func)
    def func_wrapper(inst, *args, **kwargs):
        if inst.logged_in_user:
            # Create an internal ID for this request
            request_id=uuid.uuid4()
            current_request = lambda: func(inst, *args, **kwargs)
            # Add tuple with current user, and request to be performed
            # to the pending request dictionary
            inst.pending_requests[request_id] = (
                inst.logged_in_user['data']['id'], 
                current_request)
            # Add current request id to kwargs of function being called
            kwargs['_request_id'] = request_id
        return func(inst, *args, **kwargs)
    return func_wrapper

Подводя итог, я проверяю, залогинен ли пользователь, и только потом сохраняю запрос на отправку, полностью с переданными аргументами и всем остальным, в лямбда-функцию и сохраняю в словаре pending_requests с уникальным uuid в качестве ключа. Я ввожу этот уникальный uuid в kwargs (с ключом _request_id) вызываемой функции, чтобы в случае успешного выполнения запроса я мог использовать этот uuid для удаления его из словаря, содержащего ожидающие запросы (другими словами: чистый вверх). Вы можете сделать это в функции обратного вызова вашего QNetworkReply, если она завершится без проблем.

Однако, если запрос не выполнен успешно, пользователю предоставляется окно входа в систему, и если он или она снова успешно входит в систему, вызывается функция, которая просматривает словарь ожидающих запросов и пытается выполнить снова:

def handle_login(self):
    """ Handles the login event received after login. """
    self.get_logged_in_user(self.set_logged_in_user)

def set_logged_in_user(self, user_data):
    """ Callback function for handle_login; get_logged_in_user"""
    # Parse received data about logged in user
    self.logged_in_user = json.loads(safe_decode(user_data.readAll().data()))

    # If user had any pending requests from previous login, execute them now
    for (user_id, request) in self.pending_requests.values():
        if user_id == self.logged_in_user['data']['id']:
            request()
    self.pending_requests = {}

Причина, по которой я использую словарь, заключается в том, что в Qt можно отправлять несколько запросов параллельно, и, конечно, все они могут завершиться ошибкой, когда пользователю необходимо повторно аутентифицироваться. Я хочу, чтобы все они были опробованы снова после того, как тот же пользователь снова войдет в систему.

Как я уже говорил ранее, я знаю, что это Python, который работает совершенно иначе, чем C++ (о котором я почти ничего не знаю), но я надеюсь, что он достаточно хорошо иллюстрирует принцип, чтобы можно было представить аналогичную структуру в C++.

person Daniel Schreij    schedule 25.04.2016