TonamiLog

ゆるキャン△とスノーボードとオタク

python:requestsを使ってリクエストボディのついたGETリクエストを送出する

トナミです。表題通りの事します。


GETリクエストにリクエストボディつけて送ってくれと言われてンェー何それという気持ちが発生しました。

調べてみるとGETリクエストにおけるリクエストボディの取扱はRFCに記述がありません(=未定義です)。

なんでまー、クライアントにしろサーバにしろ、好きに実装してねという状態のようです。

ソースコードの組み合わせで君だけの最強のGETリクエストにおけるリクエストボディの取扱を実装しろ!


というわけでやります。とは言え一般的でないため、簡単に調べても出てきません。

この手の話題「pythonでHTTP通信!」みたいなエントリーがヒットしまくりググラビリティが低すぎで涙出ます。


とりあえず普段使ってるrequestsさんで出来ないか考えましょう。requestsの基本的な使い方は省略します。
postの時はリクエストボディを付与出来て、getの時は普通にやると付与されないという認識があります。
まず両者の違いですが

def get(url, params=None, **kwargs):
    r"""docstring長いので省略
    """

    kwargs.setdefault('allow_redirects', True)
    return request('get', url, params=params, **kwargs)

これがget

def post(url, data=None, json=None, **kwargs):
    r"""docstring長いので省略
    """

    return request('post', url, data=data, json=json, **kwargs)

これがpost

という感じです。
キーワード引数が違うだけで、最終的に全部突っ込んでrequest()している事が分かります。

ここでrequestが何かと言うと

def request(method, url, **kwargs):
    """
    (docstringには他にも色々と大事な事が書いてあるんですが、長かったので抜粋)

    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
        object to send in the body of the :class:`Request`.
    """

    # By using the 'with' statement we are sure the session is closed, thus we
    # avoid leaving sockets open which can trigger a ResourceWarning in some
    # cases, and look like a memory leak in others.
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)

という感じになってます。
結局mthodの種類とrequest投げる先の情報以外は全部突っ込まれているということです。
んでdocstringを見ると、paramsはquery stringへ、dataはbodyへ行くぜ!という事が書いてあります。わかりやすい
(余談:docstringにコピペミスがあってoss貢献チャンスかと思いきや、自分のrequsetsがちょっと古かっただけで修正済みでした。)

つまり考えはこうです。
postの時にリクエストボディが設定されてるのは、dataに要素を渡しているから。
getの時にリクエストボディが生まれないのは、paramsにしか要素を渡していないから。
kwargsのところに入れてしまえば、どんなパラメータも最後は同じ出口から送出されていく。
んじゃgetリクエスト打つ時にdataを定義すればリクエストボディにしてもらえるんじゃねえの?

実際にやってみます

from flask import Flask, request
import json

app = Flask(__name__)

@app.route('/test', methods=['GET'])
def test():
    body = json.loads(request.data)
    print(body)

    return body['test']

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080, debug=True)

こんな感じに適当に立てたflaskサーバにリクエストを投げ、'bodydata'が取り出せるか確認します。
※flaskにおいてリクエストボディの内容はrequset.dataで取り出せるということになっています。
私は甘いのでここの検証をしていません

投げるソースはこんな

import requests
import json

url = 'http://localhost:8080/test'
data = {'test': 'bodydata'}
response = requests.get(url, data=json.dumps(data))
print(response.text)

サーバ側のソースを実行するとlocalhost:8080にFlaskくんが立つので、投げるソースを実行して似非通信をしてます。
localhostに立ってるサーバに同一ホストからリクエスト投げた、というのが検証として妥当なのか検証していません。

上記ソースをアレするとサーバ側でもクライアント側でもいい感じの値が取れてる事を確認出来ます。
これでGETリクエストにリクエストボディをつける事が出来、この問題を解く事が出来ました。よかったですね

マサカリお待ちしております。
おわり