たのしい駆動開発

たのしいアウトプットの場所

requests.Session をシングルトンで書いて使い回す

シングルトンの実装をしたrequests.Sessionクラスを使うことで、毎回同じ識別子のクラスを使い回すことが出来ます。
そうすることで、リクエストを飛ばすたびに毎回ホストとセッションをつなぐ必要がなくなるので、めちゃ便利です。


import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry


class RequestSession():
    _has_instance = None
    def __new__(cls):
        if not cls._has_instance:
            cls._has_instance = super(RequestSession, cls).__new__(cls)
            cls.session = cls.create_session()
        return cls._has_instance
    @staticmethod
    def create_session():
        session = requests.Session()
        retries = Retry(total=1)  # このへんは別になくてもOK
        session.mount("http://", HTTPAdapter(max_retries=retries))
        return session


では次にこれがちゃんとシングルトンになっているのかを確認します。

>>> rs = RequestSession()
>>> id(rs)
140271749279264
>>> rs2 = RequestSession()
>>> id(rs2)
140271749279264

識別子の値が一緒なので、同じインスタンスだとわかります。


ネットワークを確認する

ではこれを使用して、ネットワークを確認します。
しかし、Mac上にssコマンドやwatchコマンドがないので、Dockerコンテナ上で動かします。

FROM python:3

RUN apt-get -y install iproute2 watch

RUN pip install --upgrade pip
RUN pip install requests


次に、下記コマンドを叩きます。これで TCP の情報をリアルタイムで確認することが出来ます。

docker container run -it <IMAGE ID> bash
watch ss -aot


別窓でインタラクティブモードでpythonを起動します。

docker ps
docker exec -it <CONTAINER ID> bash
python


TCP の状態を確認するために、まずは通常の get を使います

import requests
requests.get("https://google.com")
requests.get("https://google.com")
requests.get("https://yahoo.co.jp")


別窓のssコマンドにはこのように表示されたかと思います。

State       Recv-Q   Send-Q     Local Address:Port         Peer Address:Port

TIME-WAIT   0        0             172.17.0.2:.....      172.217.25.196:https
timer:(timewait,49sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      216.58.220.142:https
timer:(timewait,49sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      216.58.220.142:https
timer:(timewait,50sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      216.58.220.142:https
timer:(timewait,50sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      172.217.25.196:https
timer:(timewait,50sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      216.58.220.142:https
timer:(timewait,49sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      172.217.25.196:https
timer:(timewait,49sec,0)
TIME-WAIT   0        0             172.17.0.2:.....      172.217.25.196:https
timer:(timewait,50sec,0)


次はシングルトンで書いたほうでgetしてみます。

rs = RequestSession()
rs.session.get("https://google.com")
rs.session.get("https://google.com")
rs.session.get("https://yahoo.co.jp")


State   Recv-Q    Send-Q       Local Address:Port          Peer Address:Port
ESTAB   0         0               172.17.0.2:.....       172.217.25.196:https
ESTAB   0         0               172.17.0.2:.....       216.58.220.142:https


requests.get()session.get()で、TCPのコネクション数がかなり違うかと思います。



シングルトンで実装することで何が嬉しいのか ()

ローカルから外部サーバーに通信するとき、ルーターのNAPT機能を使用しています。
ただ、リクエストをしすぎてポート番号が枯渇してしまった場合、色々と問題が起きてしまいます。
そのため、ホストとのセッションを維持し続けて通信することで、上記問題は多少増しになるよね!といった感じです。
(上記で説明したように、requests.get()などは毎回セッション繋ぎに行ってます)
このあたりをより詳しく知りたい方はKeep Aliveで調べると面白いと思います。




おまけ

2020年も最後ですね。最後になにかやっときたいな〜〜〜〜けどめんどくさいな〜〜〜と思ってたら社内の人からこれネタにできるやんと言われたので書いた感じです😇
TwitterのTLは今年買ってよかったものとか2020年の振り返りばっかで溢れてますが、逆張り陰キャなのでそういうのは書きません()
今年も私のブログをご覧いただきありがとうございました。来年もよろしくお願いします🥲 みなさん良いお年を!