kekavigi.xyz

Membuat Proksi Penyinggah

Karena saya ingin bisa membaca kembali halaman di Internet tanpa koneksi Internet

Ditulis tanggal oleh A. Keyka Vigiliant. Revisi terakhir pada tanggal . Konten diterbitkan dibawah lisensi CC BY-SA 4.0.


Mungkin karena saya tumbuh bersama laptop yang sederhana, saya selalu mencari cara mengoptimasi penggunaan RAM. Contohnya di peramban (browser), saya jarang membuka lebih dari lima-enam tab sekaligus; karena saya pernah mengalami rasanya putus asa, ketika laptop kala itu mendadak tidak responsif, dan suara kipas internalnya semakin nyaring seperti pesawat mau lepas landas. Kebiasaan itu terbawa sampai saat ini. Ketika debugging, saya suka membaca dokumentasi, tulisan blog orang, atau sekadar mampir ke forum StackOverflow. Namun ketimbang membuka semua pranala di tab-tab baru, saya terbiasa untuk membaca tuntas konten di tab tersebut; menutupnya lalu membuka pranala baru; dan mengulangi alur tersebut. Terkadang saya beberapa kali menutup-membuka suatu halaman yang sama, karena ada detail yang saya lupakan (contoh, ketika membaca dokumentasi SQLAlchemy dan rclone). Malangnya, ini malah memperbesar total penggunaan Internet. Selain itu, saya tinggal di daerah Indihome dan PLN sering bermasalah; ketika koneksi Internet terputus, saya tidak bisa membaca kembali halaman yang baru saja ditutup.

Jadi sederhananya, saya butuh cara untuk menyimpan semua halaman yang (pernah) saya akses, dan menyajikan halaman versi simpanan ketika koneksi Internet terputus. Terdengar sederhana, tetapi sebenarnya tidak; karena jika memang mudah, tidak ada yang menghentikan negara, perusahaan tempat Anda bekerja, penyedia jasa internet, dan bahkan alat-alat yang memungkinkan Anda mengakses Internet (seperti router WiFi dan smartphone), untuk diam-diam menyimpan semua halaman yang Anda akses. Ada satu cara menjawab masalah itu, yakni dengan menerapkan man-in-the-middle (MITM) attack; menyusup dan menjadi perantara peramban (browser) dengan Internet, seperti tukang pos yang diam-diam membuka surat dari seseorang, membaca (atau juga mengubah) isinya, sebelum mengirimkannya ke tujuan. Melakukan MITM sendiri dari nol adalah hal yang sulit, karena semua pihak yang berperan dalam mengelola keamanan di Internet, selalu mencari cara untuk menghindari itu bisa terjadi. Untungnya khusus untuk masalah saya, ada modul(?) mitmproxy yang bertindak sebagai proksi dan menyediakan abstraksi dalam melakukan MITM.

Memasang (installing) dan menggunakan mitmproxy cukup mudah. Namun tergantung bagaimana Anda mengatur sistem operasi di laptop dan peramban yang Anda pakai, mungkin ada beberapa langkah yang perlu ditambah/diganti. Misal kalau Anda menggunakan Linux dengan peramban Librewolf (Firefox dengan pengaturan privasi dan keamanam yang lebih baik), Anda perlu meng-import sertifikat mitmproxy ke Certificate Manager dan mengubah pengaturan security.cert_pinning.enforcement_level, agar Librewolf tidak mengganggap mitmproxy berbahaya, dan mau terhubung ke Internet.

Baik, sekarang mari membahas mitmproxy itu sendiri. Modul mitmproxy memungkinkan kita membuat addons (dengan bahasa Python) untuk menambahkan hal yang bisa dilakukan proksi. Malangnya, dokumentasi modul ini tidak lengkap, dan terkadang kita perlu menelusuri isu Github untuk mengetahui jawabannya. mitmproxy menyimpan semua informasi terkait suatu transaksi HTTP dalam objek HTTPFlow, yang selanjutnya saya sebut flow. Dari banyak event hooks yang diabstraksi mitmproxy, ada tiga hooks yang kita perlukan:

Dalam kondisi ideal, proksi kita hanya perlu menyinggah semua flow saat response() dijalankan, misal ke database SQLite. Ketika koneksi Internet terputus, hook error() akan terpanggil, dan kita menampilkan flow yang tersimpan di database, jika URL dari request kita singgah. Sialnya, kita tidak bisa mengubah konten galat HTML ketika hook error() terjadi, kecuali dengan monkey-patching serius, yang mungkin bisa saja rusak setiap ada versi baru dari mitmproxy. Saya tidak mau repot, dan mengambil solusi jalan pintas berikut:

Ini script yang menyelesaikan masalah saya:

from os import listdir

class CachingProxy:
    def __init__(self) -> None:
        self.counter = 0

    async def running(self) -> None:
        # Database menggunakan aiosqlite, flow disimpan
        # sebagai pickle yang dikompresi dengan zlib

        # CREATE TABLE IF NOT EXISTS dict(
        #     host         TEXT NOT NULL,
        #     path         TEXT NOT NULL,
        #     content-type TEXT NOT NULL,
        #     timestamp    REAL NOT NULL,
        #     response     BLOB);
        # CREATE UNIQUE INDEX IF NOT EXISTS url ON dict(host,path);
        # PRAGMA foreign_keys = ON;
        # PRAGMA journal_mode = wal;
        # PRAGMA wal_autocheckpoint;
        # PRAGMA synchronous = normal;
        # PRAGMA temp_store = memory;
        # PRAGMA mmap_size = 30000000000;
        # PRAGMA busy_timeout = 5000;
        # PRAGMA optimize

        self.cache = await Database.open("history.sqlite")

    async def request(self, flow: http.HTTPFlow) -> None:
        if "use_stale" in listdir():
            result = await self.cache.get_response(flow.request.url)
            if result:
                flow.response = result

    async def response(self, flow: http.HTTPFlow) -> None:
        # coba singgah HTTP response yang didapat; ini menghasilkan
        # False jika flow: isinya kosong, dari host/path yang ada
        # di blocklist (situs iklan, path CAPTCHA, dst.) atau berisi
        # content-type yang ada di blocklist (contoh, saya tidak
        # ingin menyinggah konten video, berkas PDF, dst.)
        if await self.cache.cache_response(flow):

            # Agar pengalaman ber-Internet terasa cepat, commit
            # ke database hanya pada saat tertentu saja, misal
            # setelah ada 64 URL baru yang disinggah.
            if not (self.counter % 64):
                self.counter = 0
                await self.cache.commit()
            self.counter += 1

    async def done(self) -> None:
        await self.cache.commit()
        await self.cache.close()

Untuk mencoba addons proksi itu, Anda perlu menjalankan

mitmdump -p 8888 -s cache.py --quiet --set connection_strategy=lazy

dan mengatur peramban agar menggunakan proksi di alamat localhost:8888.

Proksi ini sederhana, dan saya tidak ingin over-engineering it. Jika Anda ingin meningkatkannya, Anda dapat mengembangkan: dashboard agar pengguna bisa mengatur/menghapus singgahan yang tersimpan, maupun metode hemat-memori untuk menyimpan URL dengan kueri di path-nya. Setidaknya sekarang, saya bisa membaca kembali sejarah kunjungan di peramban saya tanpa perlu koneksi Internet.