httplib не отправит никакого тела, если параметр 'тело' запроса () не способен len()?

Код как это:

import httplib
import cStringIO

s = cStringIO.StringIO("hello world") 
c = httplib.HTTPConnection("xxx.xxx.xxx.xxx")
c.request("POST", "/xpost", s)

s является способным для чтения () объектом, поэтому request() должен отправлять содержимое sОднако причиной s не в состоянии len (), поэтому данные, отправляемые методом request(), не содержат заголовок Content-Length и ни один контент тела в body площадь.

Итак, как я могу отправить POST, пока я не могу получить длину body отправлять?

1 ответ

Поведение httplib (по крайней мере в Python 2.7) таков, что он проверяет, существует ли Content-Length заголовок перед добавлением его собственного, так что если вы знаете размер содержимого, вы можете добавить свой собственный заголовок - например:

c.request("POST", "/xpost", s, headers={"Content-Length": len(s.getvalue())})

Если такого заголовка нет, httplib пытается позвонить len() заполнить один автоматически, и если это не удается, предполагается, что тело должно быть объектоподобным объектом и вызывает os.fstat() на файловом дескрипторе уровня ОС, чтобы определить его размер - он получает этот дескриптор, вызывая fileno() метод на файловом дескрипторе вы дали это. Это прекрасно работает для реальных файлов, но так как StringIO объекты не являются реальными файлами, они не обеспечивают fileno() метод и операция завершается с AttributeError, Эта ошибка ловится и молча обрабатывается httplib, который просто не может добавить Content-Length,

Если вы определенно используете StringIO объекты, ваш самый простой вариант, вероятно, добавить свой собственный Content-Length заголовок, как я показал в моем примере выше. Если это всего лишь тест, пока вы работаете, и вы будете использовать реальные файлы в реальности, вы можете положиться на httplib правильно установить заголовок, пока os.fstat() работает на вашей платформе. Если нет, вы всегда можете позвонить os.stat() самостоятельно на имя файла и укажите свой собственный заголовок таким же образом.

Если вы хотите обрабатывать как реальные файлы, так и StringIO тогда вы всегда можете сделать что-то вроде этого:

headers = {}
if not hasattr(body, "fileno"):
    headers["Content-Length"] = len(body.getvalue())

... Но я не предлагаю вам добавлять эту сложность, если вам это не нужно.

Наконец, на уровне HTTP есть еще один вариант, который заключается в использовании чанкованного кодирования, когда вам не нужно указывать Content-Length заголовок, само тело закодировано в виде кусков данных с самоописанием. К сожалению, однако, много программного обеспечения HTTP и клиент и сервер (httplib включается) имеет тенденцию предполагать, что только ответы будут разделены на части, и запросы всегда будут использовать Content-Length, Я предполагаю, что это предположение, потому что запросы, как правило, небольшие, но, конечно, с POST а также PUT это предположение не выдерживает критики.

Предполагая, что вы уверены, что ваш сервер будет обрабатывать фрагментированные запросы, вы можете попробовать это - для этого вам нужно создать StringIO объект (или что-либо еще без fileno() способ победить httplibавтомат Content-Length вставка) с кодированной кодировкой, уже размещенной, и предоставьте свою собственную Transfer-Encoding заголовок со значением chunked, Лично я бы не рекомендовал это, если вы нацеливаете свое программное обеспечение на работу с широким спектром серверов.

РЕДАКТИРОВАТЬ: В качестве отступления, если вы используете чанкованное кодирование, вы не должны отправлять Content-Length заголовок - см. HTTP RFC §4.4, пункт 3. Разумеется, с помощью запроса вы не можете сигнализировать о конце тела, просто закрыв соединение, потому что тогда у вас не будет соединения для получения ответа.

В качестве примера слабой поддержки чанкованных запросов nginx добавил ее только к основной функциональности в версии 1.3.9 в конце прошлого года (хотя до этого был плагин для нее).

РЕДАКТИРОВАТЬ 2:

Если вы прочитаете статью в Википедии, то увидите, что в ней есть нечто большее, чем просто отправка правильного заголовка - вы должны разделить тело на куски и отправить каждый с маленьким заголовком, состоящим из размера фрагмента в шестнадцатеричном формате. Обычно это делается для вас при отправке ответов, но, как я уже говорил, поддержка в запросах плохая.

Вот пример оболочки вокруг файлового объекта, который преобразует тело в куски. Я адаптировал ваш пример выше, чтобы показать, как его использовать, хотя, конечно, "hello world" Тело настолько маленькое, что в конечном итоге становится только одним куском. Это должно работать для тел любого размера, как бы то ни было. Он должен работать на любом объекте с read() метод, который работает так же, как Python file объект. Фактически, если вы поместите стандартный объект файла Python в один из них, это предотвратит httplib добавив Content-Length потому что это не поддерживает len() или же fileno(),

Вы все еще должны помнить, чтобы добавить Transfer-Encoding Заголовок себя, как показано в моем примере ниже:

import httplib
import cStringIO

class ChunkedEncodingWrapper(object):

    def __init__(self, fileobj, blocksize=8192):
        self.fileobj = fileobj
        self.blocksize = blocksize
        self.current_chunk = ""
        self.closed = False

    def read(self, size=None):
        ret = ""
        while size is None or size >= len(self.current_chunk):
            ret += self.current_chunk
            if size is not None:
                size -= len(self.current_chunk)
            if self.closed:
                self.current_chunk = ""
                break
            self._get_chunk()
        else:
            ret += self.current_chunk[:size]
            self.current_chunk = self.current_chunk[size:]
        return ret

    def _get_chunk(self):
        if not self.closed:
            chunk = self.fileobj.read(self.blocksize)
            if chunk:
                self.current_chunk = "%x" % (len(chunk),) + "\r\n" + chunk + "\r\n"
            else:
                self.current_chunk = "0\r\n\r\n"
                self.closed = True


s = cStringIO.StringIO("hello world")
w = ChunkedEncodingWrapper(s)
c = httplib.HTTPConnection("xxx.xxx.xxx.xxx")
c.request("POST", "/xpost", w, headers={"Transfer-Encoding": "chunked"})
Другие вопросы по тегам