在没有原始数据的情况下重新发送 post/put 请求
Posted
技术标签:
【中文标题】在没有原始数据的情况下重新发送 post/put 请求【英文标题】:Resending a post/put request without having the original data 【发布时间】:2014-06-23 15:20:39 【问题描述】:最近有个问题一直困扰着我。我正在创建一个简单的 Web 浏览器来与单个网站进行交互,以便对产品进行用户管理。我目前遇到的问题是:用户可以通过带有基本 Web 表单的 ui 上传新文件。选择上传后,请求会失败,错误码为205,即:
QNetworkReply::ContentReSendError - 需要再次发送请求,但失败,例如因为无法再次读取上传数据。
如果用户再次尝试上传,文件将正确上传。为了解决这个问题,我想检测是否有错误,如果有,请重新发送请求。
我的问题是我没有与请求相关的数据。我可以检索请求本身,但不能检索数据。我想我的解决方案看起来像 this stack overflow question: 中的嵌套 if
if((reply->error() == QNetworkReply::TemporaryNetworkFailureError) || (reply->error() == QNetworkReply::ContentReSendError))
// retry the last request
_reply = accessManager.put(reply->request(), _currentItem);
但是,该用户可以访问 _currentItem,因为他们最初在他们的程序中创建了请求,而我的请求是动态创建的。有什么方法可以捕获正在发送的数据,或者缓存它?我曾希望 QNetworkAccessManager 存储以前的请求以便轻松重试,但我没有看到类似的东西。感谢您的任何见解和帮助!
编辑
为了澄清:无论如何使用 QNetworkAccessManager 来获取程序中未定义的 post/put 的请求和数据。
【问题讨论】:
【参考方案1】:是的,有两种方法,但该功能不存在。
编辑Qt的源代码直接添加到QNetworkAccessManager
,或
将其实现为在同一进程中运行的仅缓存 HTTP 代理。代理的实现可以使用嵌套的QNetworkAccessManager
来执行真实网络上的请求。实现需要解码传入的请求,重新构建QNetworkRequest
标头,并将其与任何请求数据一起传递给嵌套管理器。此时,您可以搁置数据,并在代理级别实现重试。
实施选项 1 可能会减少工作量和麻烦,但有些项目不合理地反对修改 Qt,所以我将选项 2 用于这种情况。
【讨论】:
啊,真不幸。我会继续寻找另一种解决方案。感谢您的回复!【参考方案2】:将 Python 与 PyQt 结合使用,我通过使用函数装饰器解决了类似的问题,该函数装饰器将请求保存在 lambda 函数中,并在失败时重新发送。在 C++ 中可能有类似的结构。
在我的特殊情况下,应用程序必须使用 OAuth2 系统,该系统分发的令牌仅在一小时内有效(并且无法刷新;隐式授权)。因此,可能是在这个小时后,用户会突然收到对他的一个请求的“未通过身份验证”响应,这当然不是真正的用户友好。所以我想,我会捕获这个 401 响应,让用户重新认证,然后重新发送最后一个失败的请求。
在我的 QNetworkAccessManager 子类中,我用一个名为 requires_internet_connection 的函数修饰了我的 HTTP 函数(get、put、post 等),因此我的 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
总结起来,我检查用户是否登录,然后我将要发送的请求(完全包含传递的参数和所有)存储在 lambda 函数中,并将其存储在具有唯一 uuid 的 pending_requests 字典中钥匙。我将这个唯一的 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++ 中的类似结构
【讨论】:
以上是关于在没有原始数据的情况下重新发送 post/put 请求的主要内容,如果未能解决你的问题,请参考以下文章
你可以在没有模型的情况下在 Django 中运行原始 MySQL 查询吗?