python3 - python httpconnection exception
IncompleteLeer usando httplib (2)
Al final del día, todos los demás módulos ( feedparser
, mechanize
y urllib2
) llaman a httplib
que es donde se lanza la excepción.
Ahora, primero lo primero, también descargué esto con wget y el archivo resultante tenía 1854 bytes. A continuación, probé con urllib2
:
>>> import urllib2
>>> url = ''http://hattiesburg.legistar.com/Feed.ashx?M=Calendar&ID=543375&GUID=83d4a09c-6b40-4300-a04b-f88884048d49&Mode=2013&Title=City+of+Hattiesburg%2c+MS+-+Calendar+(2013)''
>>> f = urllib2.urlopen(url)
>>> f.headers.headers
[''Cache-Control: private/r/n'',
''Content-Type: text/xml; charset=utf-8/r/n'',
''Server: Microsoft-IIS/7.5/r/n'',
''X-AspNet-Version: 4.0.30319/r/n'',
''X-Powered-By: ASP.NET/r/n'',
''Date: Mon, 07 Jan 2013 23:21:51 GMT/r/n'',
''Via: 1.1 BC1-ACLD/r/n'',
''Transfer-Encoding: chunked/r/n'',
''Connection: close/r/n'']
>>> f.read()
< Full traceback cut >
IncompleteRead: IncompleteRead(1854 bytes read)
Entonces está leyendo todos los 1854 bytes pero luego piensa que hay más por venir. Si le decimos explícitamente que lea solo 1854 bytes, funciona:
>>> f = urllib2.urlopen(url)
>>> f.read(1854)
''/xef/xbb/xbf<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">...snip...</rss>''
Obviamente, esto solo es útil si siempre sabemos la longitud exacta antes de tiempo. Podemos usar el hecho de que la lectura parcial se devuelve como un atributo en la excepción para capturar todo el contenido:
>>> try:
... contents = f.read()
... except httplib.IncompleteRead as e:
... contents = e.partial
...
>>> print contents
''/xef/xbb/xbf<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">...snip...</rss>''
Esta publicación del blog sugiere que esto es un error del servidor y describe cómo aplicar un parche al método httplib.HTTPResponse.read()
con el bloque try..except
anterior para manejar las cosas detrás de la escena:
import httplib
def patch_http_response_read(func):
def inner(*args):
try:
return func(*args)
except httplib.IncompleteRead, e:
return e.partial
return inner
httplib.HTTPResponse.read = patch_http_response_read(httplib.HTTPResponse.read)
feedparser
el parche y luego feedparser
funcionó:
>>> import feedparser
>>> url = ''http://hattiesburg.legistar.com/Feed.ashx?M=Calendar&ID=543375&GUID=83d4a09c-6b40-4300-a04b-f88884048d49&Mode=2013&Title=City+of+Hattiesburg%2c+MS+-+Calendar+(2013)''
>>> feedparser.parse(url)
{''bozo'': 0,
''encoding'': ''utf-8'',
''entries'': ...
''status'': 200,
''version'': ''rss20''}
Esta no es la mejor manera de hacer las cosas, pero parece funcionar. No soy lo suficientemente experto en los protocolos HTTP para decir con seguridad si el servidor está haciendo las cosas mal, o si httplib
está mal manejando un caso de borde.
He tenido un problema persistente al obtener un feed RSS de un sitio web en particular. Terminé escribiendo un procedimiento bastante feo para realizar esta función, pero tengo curiosidad por saber por qué sucede esto y si las interfaces de nivel superior manejan este problema correctamente. Este problema no es realmente un tapón de show, ya que no necesito recuperar el feed muy a menudo.
He leído una solución que atrapa la excepción y devuelve el contenido parcial, pero como las lecturas incompletas difieren en la cantidad de bytes que realmente se recuperan, no tengo la certeza de que dicha solución realmente funcione.
#!/usr/bin/env python
import os
import sys
import feedparser
from mechanize import Browser
import requests
import urllib2
from httplib import IncompleteRead
url = ''http://hattiesburg.legistar.com/Feed.ashx?M=Calendar&ID=543375&GUID=83d4a09c-6b40-4300-a04b-f88884048d49&Mode=2013&Title=City+of+Hattiesburg%2c+MS+-+Calendar+(2013)''
content = feedparser.parse(url)
if ''bozo_exception'' in content:
print content[''bozo_exception'']
else:
print "Success!!"
sys.exit(0)
print "If you see this, please tell me what happened."
# try using mechanize
b = Browser()
r = b.open(url)
try:
r.read()
except IncompleteRead, e:
print "IncompleteRead using mechanize", e
# try using urllib2
r = urllib2.urlopen(url)
try:
r.read()
except IncompleteRead, e:
print "IncompleteRead using urllib2", e
# try using requests
try:
r = requests.request(''GET'', url)
except IncompleteRead, e:
print "IncompleteRead using requests", e
# this function is old and I categorized it as ...
# "at least it works darnnit!", but I would really like to
# learn what''s happening. Please help me put this function into
# eternal rest.
def get_rss_feed(url):
response = urllib2.urlopen(url)
read_it = True
content = ''''
while read_it:
try:
content += response.read(1)
except IncompleteRead:
read_it = False
return content, response.info()
content, info = get_rss_feed(url)
feed = feedparser.parse(content)
Como ya se dijo, este no es un problema de misión crítica, sino una curiosidad, ya que aunque puedo esperar que urllib2 tenga este problema, me sorprende que este error se encuentre también en la mecanización y las solicitudes. El módulo feedparser ni siquiera produce un error, por lo que la comprobación de errores depende de la presencia de una clave ''bozo_exception''.
Edición: solo quería mencionar que tanto wget como curl realizan la función de manera impecable, recuperando la carga completa correctamente cada vez. Todavía tengo que encontrar un método de python puro para funcionar, excepto por mi feo hackeo, y estoy muy curioso por saber qué está pasando en el backend de httplib. Por otra parte, decidí probar esto también con twill el otro día y obtuve el mismo error httplib.
PS Hay una cosa que también me parece muy extraña. IncompleteRead ocurre de manera consistente en uno de los dos puntos de interrupción en la carga útil. Parece que feedparser y las solicitudes fallan después de leer 926 bytes, pero mecanizar y urllib2 fallan después de leer 1854 bytes. Este comportamiento es consistente, y me quedo sin explicación ni entendimiento.
Lo averiguo en mi caso, envío una solicitud HTTP / 1.0, soluciono el problema, simplemente agregando esto al código:
import httplib
httplib.HTTPConnection._http_vsn = 10
httplib.HTTPConnection._http_vsn_str = ''HTTP/1.0''
después de hacer la solicitud:
req = urllib2.Request(url, post, headers)
filedescriptor = urllib2.urlopen(req)
img = filedescriptor.read()
después de volver a http 1.1 con (para conexiones que admiten 1.1):
httplib.HTTPConnection._http_vsn = 11
httplib.HTTPConnection._http_vsn_str = ''HTTP/1.1''