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