Как опубликовать символы не ASCII, используя httplib, когда тип содержимого "application / xml" - PullRequest
6 голосов
/ 03 ноября 2011

Я реализовал модуль API Pivotal Tracker в Python 2.7. API Pivotal Tracker ожидает, что данные POST будут XML-документом, а "application / xml" - типом контента.

Мой код использует urlib / httplib для публикации документа, как показано:

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

Это приводит к исключению, когда текст XML содержит символы не 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)

Насколько я вижу, httplib._send_output создает строку ASCII для полезной нагрузки сообщения, предположительно потому, что она ожидает, что данные будут закодированы в URL (application / x-www-form-urlencoded). Он отлично работает с application / xml, если используются только символы ASCII.

Существует ли простой способ публикации данных application / xml, содержащих символы, отличные от ASCII, или мне придется перепрыгивать через обручи (например, с использованием Twistd и собственного производителя для полезной нагрузки POST)?

Ответы [ 4 ]

7 голосов
/ 03 ноября 2011

Вы смешиваете Юникод и байтовые строки.

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

Чтобы исправить это, убедитесь, что содержимое self.headers правильно закодировано, т. Е. Все ключи, значения в headers должны быть строками байтов:

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

Примечание: кодировка символов заголовков не имеет ничего общего с кодировкой символов тела, т. Е. Текст XML может кодироваться независимо (это просто поток октетов с точки зрения http-сообщения).

То же самое относится к self.url - если он имеет тип unicode; преобразовать его в строку байтов (используя кодировку ascii).


HTTP-сообщение состоит из стартовой строки, «заголовков», пустой строки и, возможно, тела сообщения , поэтому self.headers используется для заголовков, self.url используется для стартовой строки ( здесь используется метод http) и, вероятно, для Host заголовка http (если клиент http / 1.1), текст XML отправляется в тело сообщения (в виде двоичного двоичного объекта).

Всегда безопасно использовать кодировку ASCII для self.url (IDNA может использоваться для доменных имен, не относящихся к ascii - результатом также является ASCII).

Вот что rfc 7230 говорит о http заголовках кодировке :

Исторически HTTP разрешал содержимое полей с текстом в Кодировка ISO-8859-1 [ISO-8859-1], поддерживает только другие кодировки путем использования кодировки [RFC2047]. На практике большинство заголовков HTTP Значения полей используют только подмножество кодировки US-ASCII [USASCII]. Вновь определенные поля заголовка ДОЛЖНЫ ограничивать значения их полей US-ASCII октеты. Получатель ДОЛЖЕН обращаться с другими октетами в поле содержимое (obs-text) в виде непрозрачных данных.

Чтобы преобразовать XML в строку байтов, см. application/xml condsiderations :

Рекомендуется использовать UTF-8 без спецификации для всех объектов XML MIME.

2 голосов
/ 09 июня 2013

Проверьте, является ли self.url юникодом. Если это Unicode, то httplib будет обрабатывать данные как Unicode.

вы можете принудительно закодировать self.url в unicode, тогда httplib будет обрабатывать все данные как unicode

1 голос
/ 16 апреля 2016

То же, что и ответ Дж.Ф. Себастьяна, но я добавляю новый, чтобы форматирование кода работало (и было более удобным для Google)

Вот что происходит, если вы пытаетесь пометить конец запроса механизированной формы:

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()
0 голосов
/ 04 июля 2015

Здесь необходимо рассмотреть 3 вещи

  • Строка не в Юникоде + строка в Юникоде, результат будет автоматически преобразован в строку в Юникоде.
  • Python 2.7 httplib, просто использует+ объединять заголовок с телом, которое я не считаю хорошей практикой, мы не должны доверять автоматическому преобразованию типов.но Python 2.6 httplib отличается.
  • Стандарт протокола HTTP предлагает ISO-8859-1 кодирование для заголовка, но если вы хотите поставить не ISO-8859-1 символов, вы должны закодировать его как rfc2047 , описанный

Простое решение состоит в строгом кодировании заголовка и тела в utf-8 перед отправкой.

...