¿Cómo publico caracteres no ASCII utilizando httplib cuando el tipo de contenido es “aplicación / xml”?

He implementado un módulo de API Pivotal Tracker en Python 2.7. La API de Pivotal Tracker espera que los datos POST sean un documento XML y “application / xml” sea el tipo de contenido.

Mi código usa urlib / httplib para publicar el documento como se muestra:

request = urllib2.Request(self.url, xml_request.toxml('utf-8') if xml_request else None, self.headers) obj = parse_xml(self.opener.open(request)) 

Esto produce una excepción cuando el texto XML contiene caracteres que no son ASCII:

 File "/usr/lib/python2.7/httplib.py", line 951, in endheaders self._send_output(message_body) File "/usr/lib/python2.7/httplib.py", line 809, in _send_output msg += message_body exceptions.UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 89: ordinal not in range(128) 

Tan cerca como puedo ver, httplib._send_output está creando una cadena ASCII para la carga útil del mensaje, presumiblemente porque espera que los datos estén codificados en la URL (application / x-www-form-urlencoded). Funciona bien con application / xml siempre y cuando solo se utilicen caracteres ASCII.

¿Existe una forma sencilla de publicar datos de aplicación / xml que contengan caracteres no ASCII o voy a tener que saltar a través de aros (p. Ej., Usar Twistd y un productor personalizado para la carga útil POST)?

Estás mezclando Unicode y Bytestrings.

 >>> msg = u'abc' # Unicode string >>> message_body = b'\xc5' # bytestring >>> msg += message_body Traceback (most recent call last): File "", line 1, in  UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 0: ordinal \ not in range(128) 

Para solucionarlo, asegúrese de que el contenido de self.headers esté correctamente codificado, es decir, todas las claves, los valores en los headers deben ser bytestrings:

 self.headers = dict((k.encode('ascii') if isinstance(k, unicode) else k, v.encode('ascii') if isinstance(v, unicode) else v) for k,v in self.headers.items()) 

Nota: la encoding de caracteres de los encabezados no tiene nada que ver con la encoding de caracteres de un cuerpo, es decir, el texto xml se puede codificar de forma independiente (es solo un flujo de octetos desde el punto de vista del mensaje http).

Lo mismo ocurre con self.url si tiene el tipo unicode ; conviértalo en un bytestring (usando la encoding de caracteres ‘ascii’).


El mensaje HTTP consiste en una línea de inicio, “encabezados”, una línea vacía y posiblemente un cuerpo de mensaje, por lo que los self.headers se usan para los encabezados, self.url se usa para la línea de inicio (el método http va aquí) y probablemente para el Host encabezado http (si el cliente es http / 1.1), el texto XML va al cuerpo del mensaje (como un blob binario).

Siempre es seguro usar la encoding ASCII para self.url (IDNA se puede usar para nombres de dominio que no son ascii, el resultado también es ASCII).

Esto es lo que rfc 7230 dice acerca de la encoding de caracteres de los encabezados http :

Históricamente, HTTP ha permitido el contenido de campo con texto en el conjunto de caracteres ISO-8859-1 [ISO-8859-1], que admite otros conjuntos de caracteres solo mediante el uso de la encoding [RFC2047]. En la práctica, la mayoría de los valores de campo de encabezado HTTP usan solo un subconjunto del conjunto de caracteres US-ASCII [USASCII]. Los campos de encabezado recién definidos DEBERÍAN limitar sus valores de campo a octetos US-ASCII. Un destinatario DEBERÍA tratar otros octetos en el contenido del campo (texto obs.) Como datos opacos.

Para convertir XML en un bytestring, consulte condsiderations de encoding de la application/xml :

El uso de UTF-8, sin una lista de materiales, se RECOMIENDA para todas las entidades MIME XML.

Compruebe si el self.url es unicode. Si es unicode, entonces httplib tratará los datos como unicode.

podría forzar la encoding self.url a unicode, luego httplib tratará todos los datos como unicode

Igual que la respuesta de JF Sebastian, pero estoy agregando una nueva para que el formato de código funcione (y es más compatible con Google)

Esto es lo que sucede si intenta etiquetar al final de una solicitud de formulario de mecanización:

 br = mechanize.Browser() br.select_form(nr=0) br['form_thingy'] = u"Wonderful" headers = dict((k.encode('ascii') if isinstance(k, unicode) else k, v.encode('ascii') if isinstance(v, unicode) else v) for k,v in br.request.headers.items()) br.addheaders = headers req = br.submit() 

Hay 3 cosas para cubrir aquí

  • Cadena no Unicode + cadena Unicode, el resultado se convertirá en una cadena Unicode automáticamente.
  • Python 2.7 httplib, simplemente usa + para unir el encabezado con el cuerpo, lo que no creo que sea una buena práctica, no debemos confiar en la conversión automática de tipos. pero Python 2.6 httplib es diferente.
  • El estándar de protocolo HTTP sugiere la encoding ISO-8859-1 para el encabezado, pero si desea colocar caracteres que no sean ISO-8859-1 , debe codificarlos como se describe a rfc2047

La solución simple es codificar estrictamente tanto el encabezado como el cuerpo para utf-8 antes de enviarlos.