2011-11-03 9 views
6

Ho implementato un modulo API di Pivotal Tracker in Python 2.7. Lo Pivotal Tracker API si aspetta che i dati POST siano un documento XML e "application/xml" sia il tipo di contenuto.Come posso inserire caratteri non ASCII utilizzando httplib quando il tipo di contenuto è "application/xml"

Il mio codice usa urlib/httplib di inviare il documento come mostrato:

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

questo produce un'eccezione quando il testo XML contiene caratteri non 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) 

Da quanto posso see, httplib._send_output sta creando una stringa ASCII per il payload del messaggio, presumibilmente perché si aspetta che i dati siano codificati URL (application/x-www-form-urlencoded). Funziona bene con application/xml purché vengano utilizzati solo caratteri ASCII.

Esiste un modo semplice per inviare dati application/xml contenenti caratteri non ASCII o sto andando a dover passare da un anello all'altro (ad esempio utilizzando Twistd e un produttore personalizzato per il carico utile POST)?

risposta

7

Stai mescolando Unicode e i segni di estorsione.

>>> 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) 

Per risolvere il problema, assicurarsi cioè che self.headers contenuto è correttamente codificato, tutte le chiavi, i valori nella headers dovrebbero essere stringhe di byte:

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: codifica dei caratteri delle intestazioni non ha nulla a che fare con una codifica di carattere di un corpo cioè, il testo xml può essere codificato in modo indipendente (è solo un flusso ottetto dal punto di vista del messaggio http).

Lo stesso vale per self.url -se ha il tipo unicode; convertirlo in un bytestring (usando la codifica dei caratteri 'ascii').


HTTP message consists of a start-line, "headers", an empty line and possibly a message-body così self.headers viene utilizzato per le intestazioni, self.url viene utilizzato per la messa in linea (metodo http va qui) e, probabilmente, per Host intestazione http (se il cliente è http/1.1), testo XML va al corpo del messaggio (come blob binario).

E 'sempre sicuro da usare la codifica ASCII per self.url (IDNA può essere utilizzato per il dominio non-ASCII nomi-il risultato è anche ASCII).

Ecco cosa rfc 7230 says about http headers character encoding:

Storicamente, HTTP ha permesso contenuto campo con testo nel charset ISO-8859-1 [ISO-8859-1], sostenendo altri set di caratteri solo attraverso l'uso di [RFC2047 ] codifica. In pratica, la maggior parte dei valori di campo dell'intestazione HTTP utilizza solo un sottoinsieme del set di caratteri US-ASCII [USASCII]. I campi di intestazione appena definiti DOVREBBERO limitare i loro valori di campo a ottetti US-ASCII. Un destinatario DOVREBBE trattare altri ottetti nel campo contenuto (obs-testo) come dati opachi.

Per convertire XML in un bytestring, vedere application/xml encoding condsiderations:

L'uso di UTF-8, senza un BOM, è consigliato per tutte le entità MIME XML.

+0

Forse potresti cambiare il tipo di contenuto delle intestazioni, ma come risolvere il problema? Il 'msg' viene costruito nelle librerie python, ed è una stringa di byte. – jro

+1

@jro: non ha nulla a che fare con HTTP. Guarda l'esempio * completo * sopra. – jfs

+0

Ho capito che questo causa il problema, ma il mio punto era che non ha il controllo sulla variabile 'msg'. Sono d'accordo con il tuo punto, ma la mia domanda è più in linea di come questo fatto possa aiutarlo a risolverlo quando nella libs 'msg' viene creato come' msg = "\ r \ n" .join (self._buffer) '? – jro

2

Verificare se lo self.url è unicode. Se è unicode, quindi httplib tratterà i dati come unicode.

si potrebbe forzare la codifica self.url a Unicode, quindi httplib tratterà tutti i dati come unicode

0

Ci sono 3 cose da coprire qui

    stringa
  • non Unicode + stringa Unicode, il risultato sarà essere convertito automaticamente in una stringa Unicode.
  • Python 2.7 httplib, utilizza semplicemente + per unire un'intestazione con un corpo che non credo sia una buona pratica, non dovremmo fidarci della conversione automatica dei tipi. ma Python 2.6 httplib è diverso. protocollo standard
  • HTTP suggerisce ISO-8859-1 codifica per l'intestazione, ma se si vuole mettere non ISO-8859-1 caratteri, si deve codificare come descritto rfc2047

La semplice la soluzione è codificare rigorosamente sia l'intestazione che il corpo in utf-8 prima di inviarlo.

1

Uguale a JF Sebastian risposta, ma sto aggiungendo uno nuovo in modo che il codice di formattazione opere (ed è più in grado di Google)

Ecco cosa succede se si sta cercando di codificare fino alla fine del una richiesta di modulo di meccanizzazione:

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() 
Problemi correlati