首页 > 解决方案 > 无法通过 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)

会修复它,但显然不是。

我不知道如何在不创建错误循环的情况下自动重启我的机器人。非常感谢任何帮助。

标签: pythonhttphttpsbotstelegram-bot

解决方案


亲爱的@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"]

推荐阅读