2011-11-03 8 views
6

Implementé un módulo Pivotal Tracker API en Python 2.7. El Pivotal Tracker API espera que los datos POST sean un documento XML y que "application/xml" sea el tipo de contenido.Cómo publico caracteres que no son ASCII usando httplib cuando content-type es "application/xml"

Mi código utiliza 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 no 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) 

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

¿Existe alguna manera directa de publicar datos de aplicación/xml que contengan caracteres que no sean ASCII o voy a tener que pasar por aros (por ejemplo, usando Twistd y un productor personalizado para la carga de POST)?

Respuesta

7

Está mezclando Unicode y cadenas de bytes.

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

Para solucionarlo, asegúrese de decir que self.headers contenido está codificado correctamente, todas las claves, los valores en la headers deben ser cadenas de bytes:

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 codificación de caracteres de los encabezados tiene nada que ver con una codificación de caracteres de un cuerpo, es decir, el texto xml puede codificarse de manera independiente (es solo una secuencia de octetos desde el punto de vista del mensaje http).

Lo mismo ocurre con self.url -si tiene el tipo unicode; conviértalo en una cadena de bytes (usando codificación de caracteres 'ascii').


HTTP message consists of a start-line, "headers", an empty line and possibly a message-body por lo self.headers se utiliza para los encabezados, self.url se utiliza para la puesta en línea (método http va aquí) y probablemente por Host cabecera HTTP (si el cliente es http/1.1), texto XML va al cuerpo del mensaje (como blob binario).

Siempre es seguro de usar la codificación ASCII para self.url (IDNA se puede utilizar para el dominio no ASCII nombres-el resultado es también ASCII).

Aquí es lo rfc 7230 says about http headers character encoding:

Históricamente, HTTP ha permitido contenido del campo con el texto en el juego de caracteres ISO-8859-1 [ISO-8859-1], el apoyo a otros juegos de caracteres solamente través del uso de [RFC2047 ] codificación. En la práctica, la mayoría de los valores de campo del encabezado HTTP usan solo un subconjunto del juego de caracteres US-ASCII [USASCII]. Los campos de encabezado definidos recientemente DEBERÍAN limitar sus valores de campo a octetos US-ASCII. Un destinatario DEBERÍA tratar otros octetos en el campo contenido (obs-texto) como datos opacos.

Convertir XML a una cadena de bytes, véase application/xml encoding condsiderations:

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

+0

Tal vez podría cambiar el 'contenido type' de las cabeceras, pero ¿cómo se soluciona el problema? El 'msg' se construye en las bibliotecas de python, y es una cadena de bytes. – jro

+1

@jro: no tiene nada que ver con HTTP. Mire el ejemplo * completo * arriba. – jfs

+0

Entiendo que esto causa el problema, pero mi punto es que él no tiene control sobre la variable 'msg'. Estoy de acuerdo con su punto, pero mi pregunta está más en la línea de cómo este hecho puede ayudarlo a resolverlo cuando en las libs 'msg' se crea como' msg = "\ r \ n" .join (self._buffer) ¿? – jro

2

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

que podría forzar self.url codificación a Unicode, entonces httplib tratará a todos los datos como Unicode

0

Hay 3 cosas que se tratarán aquí

    cadena
  • no Unicode + cadena Unicode, el resultado será ser convertido en una cadena Unicode automáticamente.
  • Python 2.7 httplib, simplemente usa + para unir el encabezado con el cuerpo 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.
  • estándar de protocolo HTTP
  • sugiere ISO-8859-1 codificación de cabecera, pero si usted quiere poner no ISO-8859-1 caracteres, hay que codificarlo como rfc2047 describe

El simple La solución es codificar estrictamente tanto el encabezado como el cuerpo a utf-8 antes de enviar.

1

Igual que JF Sebastián respuesta, pero estoy añadiendo uno nuevo por lo que el formato de código que funciona (y es más capaz de Google)

Esto es lo que sucede si usted está tratando de marcar hasta el final de un mecanizar la solicitud de formulario:

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() 
Cuestiones relacionadas