python - 无法通过 http.client 正确重新连接
问题描述
我有一个 Telegram 机器人,它不断向 Telegram API 发出更新请求。
有时我会收到这样的错误:
18-12-16 12:12:37: error: Traceback (most recent call last):
File "/home/pi/MuseBot/main.py", line 157, in <module>
main()
File "/home/pi/MuseBot/main.py", line 34, in main
updates = HANDLER.makeRequest("getUpdates", {"timeout": REQUEST_DELAY, "offset": lastOffset})
File "/home/pi/MuseBot/functions.py", line 42, in makeRequest
response = self.con.getresponse()
File "/usr/lib/python3.5/http/client.py", line 1198, in getresponse
response.begin()
File "/usr/lib/python3.5/http/client.py", line 297, in begin
version, status, reason = self._read_status()
File "/usr/lib/python3.5/http/client.py", line 258, in _read_status
line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
File "/usr/lib/python3.5/socket.py", line 576, in readinto
return self._sock.recv_into(b)
File "/usr/lib/python3.5/ssl.py", line 937, in recv_into
return self.read(nbytes, buffer)
File "/usr/lib/python3.5/ssl.py", line 799, in read
return self._sslobj.read(len, buffer)
File "/usr/lib/python3.5/ssl.py", line 583, in read
v = self._sslobj.read(len, buffer)
OSError: [Errno 113] No route to host
在得到这个几次之后,我为机器人实现了自动重启——我认为这个错误不是我可以阻止的,它在服务器端。
但是我遇到了麻烦。
我有一个 API 处理程序类,可以让我更轻松地管理 API 请求。这仅在脚本运行时实例化一次,而不是在每次重新启动后。
它创建一个像这样的连接:
class ApiHandler:
def __init__(self):
self.con = http.client.HTTPSConnection(URL, 443)
当出现问题时,我会调用这个函数:
def reconnect(self):
self.con.close()
self.con = http.client.HTTPSConnection(URL, 443)
然后我使用这个处理程序发出请求:
def makeRequest(self, cmd, data={}):
jsonData = json.dumps(data)
try:
self.con.request("POST", REQUEST_URL+cmd, jsonData, HEADERS)
except:
debug("An error occurred while carrying out the API request", 1)
response = self.con.getresponse()
decodedResponse = json.loads(response.read().decode())
if not decodedResponse["ok"]:
debug("reponse: {}".format(decodedResponse), 3)
raise ApiError(decodedResponse["error_code"])
return False
return decodedResponse["result"]
(ApiError 只是一个从 Exception 构造的类,我用它来在处理崩溃时将其与其他错误区分开来。)每当我收到上述错误时,我都会自动reconnect
. 但是随后的每个makeRequest
调用都会产生此错误:
18-12-16 12:13:02: notice: An error occurred while carrying out the API request
18-12-16 12:13:02: error: Traceback (most recent call last):
File "/home/pi/MuseBot/main.py", line 157, in <module>
main()
File "/home/pi/MuseBot/main.py", line 23, in main
metaData = HANDLER.makeRequest("getMe")
File "/home/pi/MuseBot/functions.py", line 42, in makeRequest
response = self.con.getresponse()
File "/usr/lib/python3.5/http/client.py", line 1194, in getresponse
response = self.response_class(self.sock, method=self._method)
File "/usr/lib/python3.5/http/client.py", line 235, in __init__
self.fp = sock.makefile("rb")
AttributeError: 'NoneType' object has no attribute 'makefile'
然后我自动重新连接并重试,但错误再次发生,机器人进入地狱般的错误循环,未选中该循环会创建大量日志文件并使我的树莓派崩溃。唯一的解决方案通常是重新启动我的树莓派。
我会认为创建一个新的连接reconnect
:
self.con = http.client.HTTPSConnection(URL, 443)
会修复它,但显然不是。
我不知道如何在不创建错误循环的情况下自动重启我的机器人。非常感谢任何帮助。
解决方案
亲爱的@JThistle 不要亲自接受这个,但您在 makeRequest 中有一些错误,请阅读我在您的代码中的评论,然后是一个应该符合您期望的示例。
你的代码:
def makeRequest(self, cmd, data={}):
jsonData = json.dumps(data)
try:
self.con.request("POST", REQUEST_URL+ cmd, jsonData, HEADERS)
except:
# this is you main bug you do not rerise the exception so you
# never actually reconnect
debug("An error occurred while carrying out the API request", 1)
# so your program follows to this step and the socket is closed when you try
# to read from it, hence the second exception
response = self.con.getresponse()
decodedResponse = json.loads(response.read().decode())
if not decodedResponse["ok"]:
debug("reponse: {}".format(decodedResponse), 3)
raise ApiError(decodedResponse["error_code"])
# this is wrong you can not reach this return, raise exception ends execution
# and goes up the stack until it will be caught or will crash the program
return False
return decodedResponse["result"]
这应该重新引发异常,以便您的重新连接实际上可以发生:
# python uses snake case not camel case, it might seem silly but pytonistas are
# easily annoyed bunch
def make_request(self, cmd, data=None):
# it is risky to use a reference type as default argument
# https://docs.python-guide.org/writing/gotchas/
data = data or {}
json_data = json.dumps(data)
try:
self.con.request("POST", REQUEST_URL + cmd, json_data, HEADERS)
except:
debug("An error occurred while carrying out the API request", 1)
raise # important
response = self.con.getresponse()
decoded_response = json.loads(response.read().decode())
# if the 'ok' is not present in the response string you will get 'KeyError'
# so use .get("ok") to get None instead
if not decoded_response.get("ok"):
debug("reponse: {}".format(decoded_response), 3)
error = decoded_response.get("error_code")
# what if the "error_code" not in the response, response could be empty ?
raise ApiError(error)
# again response could be malformed and you will get "KeyError" if
# "result" is not present
return decoded_response["result"]
推荐阅读
- python - 循环输入直到满足参数或用户输入
- c# - Json.NET TypeNameHandling 和 TypeNameAssemblyFormatHandling
- java - 创建包含收件人、主题、正文和附件的电子邮件
- c# - Unity 中的 C#:在不阻塞主线程的情况下调用 Promise 风格的异步方法
- ubuntu - Anki 上的 LATEX 无法正常显示;Ubuntu 18.10
- c# - 如果 2 个图像匹配,如何使 2 个图片框不可见
- java - 根据条件在jsp forloop中隐藏div标签
- c# - 多级属性的 nameof() 运算符
- python - 将列表列表转换为具有多个键值的字典
- pandas - AWS Lambda 函数中缺少必需的依赖项 ['numpy']