1

次のようなコード:

import httplib
import cStringIO

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

sは read() 可能なオブジェクトであるため、 request() は のコンテンツを送信する必要がありますがssは len() に対応していないため、 request() によって送信されるデータには、ヘッダーContent-Lengthもボディ コンテンツも含まれていませんbody

では、送信する長さを取得できないときに、どうすれば POST を送信bodyできますか?

4

1 に答える 1

3

httplib(少なくとも Python 2.7 では)の動作はContent-Length、独自のヘッダーを追加する前に既存のヘッダーがあるかどうかを確認するため、コンテンツのサイズがわかっている場合は、独自のヘッダーを追加できます。たとえば、次のようになります。

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

そのようなヘッダーがない場合は、自動的に 1 つを埋めるためhttplibに呼び出しを試みlen()ます。これが失敗した場合は、本体がファイルのようなオブジェクトであると想定os.fstat()し、OS レベルのファイル記述子を呼び出してそのサイズを決定します。指定したファイルハンドルでメソッドを呼び出しfileno()ます。これは実際のファイルでは問題なく機能しますが、StringIOオブジェクトは実際のファイルではないため、fileno()メソッドを提供せず、操作はAttributeError. このエラーは によってキャッチされ、暗黙のうちに処理されhttplibます。これは単にContent-Length.

間違いなくオブジェクトを使用している場合、上記の例で示したように、独自のヘッダーStringIOを追加するのがおそらく最も簡単なオプションです。これが作業中の単なるテストであり、実際に実際のファイルを使用する場合は、プラットフォームで機能する限り、ヘッダーを正しく設定するContent-Lengthことができます。そうでない場合は、いつでもファイル名で自分自身を呼び出し、同じ方法で独自のヘッダーを指定できます。httplibos.fstat()os.stat()

両方の実際のファイルを処理したい場合StringIOは、いつでも次のようにすることができます。

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

...しかし、必要でない限り、その複雑さを追加することはお勧めしません。

最後に、HTTP レベルでは、チャンク エンコーディングを使用する別のオプションがあります。この場合、ヘッダーを指定する必要はありませんContent-Length。本文自体は、データの自己記述チャンクでエンコードされます。残念ながら、クライアントとサーバー (httplib含まれている)の両方の多くの HTTP ソフトウェアは、応答のみがチャンクされ、要求は常にContent-Length. この仮定は、リクエストが通常小さいためだと思いますが、もちろんPOSTPUTこの仮定は成立しません。

サーバーがチャンク化されたリクエストを処理できると確信している場合は、それを試すことができます-そうするには、チャンク化されたエンコーディングが既に含まれているオブジェクト(またはの自動挿入を無効にする方法がStringIOないもの)を構築する必要があります配置し、独自のヘッダーに value を指定します。個人的には、ソフトウェアがさまざまなサーバーで動作することを目指している場合、これはお勧めしません。fileno()httplibContent-LengthTransfer-Encodingchunked

編集:余談ですが、チャンク エンコーディングを使用する場合、ヘッダーを送信してはなりません。応答を受信する接続がありません。Content-Length

チャンクされたリクエストのサポートがいかに貧弱であるかの例として、nginxは昨年末にバージョン 1.3.9でそれをコア機能に追加しただけです (ただし、その前にプラグインがありました)。

編集2:

ウィキペディアの記事を読むと、単に正しいヘッダーを送信するだけでなく、もう少し多くの作業があることがわかります。本体をチャンクに分割し、チャンクのサイズ (16 進数) で構成される小さなヘッダーを付けてそれぞれを送信する必要があります。これは通常、応答を送信するときに行われますが、前述したように、要求でのサポートは不十分です。

本文をチャンクに変換する、ファイルのようなオブジェクトのラッパーの例を次に示します。上記の例を使用して使用方法を示しましたが、もちろん"hello world"本体は非常に小さいため、最終的には単一のチャンクになります。ただし、どのサイズのボディでも機能するはずです。read()Python オブジェクトと同じように機能するメソッドを持つ任意のオブジェクトで機能するはずfileです。実際、標準の Python ファイル オブジェクトをこれらのいずれかでラップすると、orがサポートされていないため、httplib追加できなくなります。Content-Lengthlen()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"})
于 2013-07-02T10:19:56.257 に答える