Возникла исключительная ситуация BadStatusLine при возврате ответа с сервера в Python 3
Я пытаюсь перенести сценарий на Python 3, который передает XML-каналы, найденные здесь:
https://developers.google.com/search-appliance/documentation/files/pushfeed_client.py.txt
После запуска 2to3.py и внесения нескольких небольших корректировок для удаления любых синтаксических ошибок скрипт завершается с этим:
(py33dev) d:\dev\workspace>python pushfeed_client.py --datasource="TEST1" --feedtype="full" --url="http://gsa:19900/xmlfeed" --xmlfilename="test.xml"
Traceback (most recent call last):
File "pushfeed_client.py", line 108, in <module>
main(sys.argv)
File "pushfeed_client.py", line 56, in main
result = urllib.request.urlopen(request_url)
File "C:\Python33\Lib\urllib\request.py", line 156, in urlopen
return opener.open(url, data, timeout)
File "C:\Python33\Lib\urllib\request.py", line 469, in open
response = self._open(req, data)
File "C:\Python33\Lib\urllib\request.py", line 487, in _open
'_open', req)
File "C:\Python33\Lib\urllib\request.py", line 447, in _call_chain
result = func(*args)
File "C:\Python33\Lib\urllib\request.py", line 1268, in http_open
return self.do_open(http.client.HTTPConnection, req)
File "C:\Python33\Lib\urllib\request.py", line 1253, in do_open
r = h.getresponse()
File "C:\Python33\Lib\http\client.py", line 1147, in getresponse
response.begin()
File "C:\Python33\Lib\http\client.py", line 358, in begin
version, status, reason = self._read_status()
File "C:\Python33\Lib\http\client.py", line 340, in _read_status
raise BadStatusLine(line)
http.client.BadStatusLine: <!DOCTYPE html>
Почему он возвращает это исключение с ответом от сервера? Вот полный ответ от GSA, когда я понюхал сессию:
<!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
<title>Error 400 (Bad Request)!!1</title>
<style>
*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}
</style>
<a href=//www.google.com/><img src=//www.google.com/images/errors/logo_sm.gif alt=Google></a>
<p><b>400.</b> <ins>That’s an error.</ins>
<p>Your client has issued a malformed or illegal request. <ins>That’s all we know.</ins>
И он вернул HTTP 400. Я могу надежно вызвать эту проблему всякий раз, когда полезная нагрузка XML содержит символ utf-8. Это работает безупречно, когда это просто ascii. Вот самая основная версия кода, которую я могу использовать, чтобы надежно воссоздать проблему:
import http.client
http.client.HTTPConnection.debuglevel = 1
with open("GSA_full_Feed.xml", encoding='utf-8') as xdata:
payload = xdata.read()
content_length = len(payload)
feed_path = "xmlfeed"
content_type = "multipart/form-data; boundary=----------boundary_of_feed_data$"
headers = {"Content-type": content_type, "Content-length": content_length}
conn = http.client.HTTPConnection("gsa", 19900)
conn.request("POST", feed_path, body=payload.encode("utf-8"), headers=headers)
res = conn.getresponse()
print(res.read())
conn.close()
А вот пример полезной нагрузки XML, которая используется для создания исключения:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE gsafeed PUBLIC "-//Google//DTD GSA Feeds//EN" "gsafeed.dtd">
<gsafeed>
<header>
<datasource>TEST1</datasource>
<feedtype>full</feedtype>
</header>
<group>
<record action="add" mimetype="text/html" url="https://myschweetassurl.com">
<metadata>
<meta content="shit happens, then you die" name="description"/>
</metadata>
<content>wacky Umläut test of non utf-8 characters</content>
</record>
</group>
</gsafeed>
Единственная разница между версиями 2 и 3, которую я могу найти, - это заголовки длины содержимого для каждого запроса. Версия Python 3 неизменно короче версии 2, 870 против 873.
1 ответ
После большого количества проволочек я выяснил причину и решение проблемы - способ установки заголовка длины содержимого. В моем порту скрипта Python 3 я скопировал метод, который установил длину содержимого. Что это такое:
headers['Content-length']=str(len(body))
Это неверно! Правильный путь будет таким:
headers['Content-length']=str(len(bytes(body, 'utf-8')))
Потому что полезная нагрузка должна быть байтовым объектом. Когда вы кодируете его байтами, длина отличается от строковой версии.
return urllib.request.Request(theurl, bytes(body, 'utf-8'), headers)
Вы можете безопасно пропустить настройку заголовка длины содержимого вручную, когда используете все, что происходит от http.client.HTTPConnection. У него есть внутренний метод, который проверяет заголовок длины содержимого, и, если он отсутствует, задайте его в зависимости от длины тела содержимого независимо от формы.
Проблема заключалась в переводе, но тонкой разнице между Python 2 и 3 и тем, как он обрабатывает строки и кодирует их. Должно быть, это была какая-то случайность, что обычная версия ASCII работала, когда версия utf-8 не работала, ну да ладно.