>pythontools
← Back to feed

website server hoster

@EliteCoder917Jun 10, 20262 files

A proxy that makes your clients browser work from your servers connection.

server.py

server side
server.py
# run this part on the server side

#!/usr/bin/env python3
"""
web.py — a password-protected HTTP/HTTPS forward proxy.

Run this on your Mac. It turns your Mac into a web proxy: any device that
points its browser at this proxy will browse the internet *through your Mac*
(using your Mac's IP address).

To make it reachable from anywhere on the internet, expose it with ngrok:

    ngrok tcp 8080

ngrok prints a public address like  tcp://7.tcp.ngrok.io:18123  — give that
host and port to the other device (see client.py).

Security: the proxy requires a username + password (HTTP proxy auth). NEVER run
an open proxy on the public internet — it will be found and abused. Change the
credentials below (or set PROXY_USER / PROXY_PASS environment variables).
"""

import base64
import os
import select
import socket
import sys
import threading

# --- configuration -----------------------------------------------------------
HOST = "0.0.0.0"           # listen on all interfaces (ngrok connects locally)
PORT = int(os.environ.get("PROXY_PORT", "8080"))
USER = os.environ.get("PROXY_USER", "username")
PASS = os.environ.get("PROXY_PASS", "change-me-please")
BUFSIZE = 65536
# -----------------------------------------------------------------------------

EXPECTED_AUTH = "Basic " + base64.b64encode(f"{USER}:{PASS}".encode()).decode()


def recv_headers(sock):
    """Read until the end of the HTTP header block. Returns raw bytes or None."""
    data = b""
    while b"\r\n\r\n" not in data:
        chunk = sock.recv(BUFSIZE)
        if not chunk:
            return None
        data += chunk
        if len(data) > 1_000_000:  # guard against runaway headers
            return None
    return data


def parse_request(raw):
    """Return (method, target, version, header_lines list, raw bytes)."""
    head, _, _rest = raw.partition(b"\r\n\r\n")
    lines = head.split(b"\r\n")
    method, target, version = lines[0].decode("latin-1").split(" ", 2)
    headers = lines[1:]
    return method, target, version, headers, raw


def find_header(headers, name):
    name = name.lower()
    for line in headers:
        k, _, v = line.decode("latin-1").partition(":")
        if k.strip().lower() == name:
            return v.strip()
    return None


def authorized(headers):
    return find_header(headers, "Proxy-Authorization") == EXPECTED_AUTH


def send_407(client):
    body = b"Proxy authentication required."
    client.sendall(
        b"HTTP/1.1 407 Proxy Authentication Required\r\n"
        b'Proxy-Authenticate: Basic realm="Mac Proxy"\r\n'
        b"Content-Length: " + str(len(body)).encode() + b"\r\n"
        b"Connection: close\r\n\r\n" + body
    )


def pipe(a, b):
    """Relay data both directions between two sockets until one closes."""
    socks = [a, b]
    try:
        while True:
            readable, _, errored = select.select(socks, [], socks, 60)
            if errored or not readable:
                break
            for s in readable:
                data = s.recv(BUFSIZE)
                if not data:
                    return
                (b if s is a else a).sendall(data)
    except (OSError, ValueError):
        pass


def handle_connect(client, target):
    """HTTPS / tunneling: 'CONNECT host:port HTTP/1.1'."""
    host, _, port = target.partition(":")
    port = int(port or 443)
    try:
        upstream = socket.create_connection((host, port), timeout=15)
    except OSError as e:
        client.sendall(f"HTTP/1.1 502 Bad Gateway\r\n\r\n{e}".encode())
        return
    client.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
    pipe(client, upstream)
    upstream.close()


def handle_plain(client, method, target, version, headers, raw):
    """Plain HTTP: target is an absolute URL like http://host/path."""
    if not target.startswith("http://"):
        client.sendall(b"HTTP/1.1 400 Bad Request\r\n\r\nUnsupported target")
        return
    rest = target[len("http://"):]
    hostport, _, path = rest.partition("/")
    path = "/" + path
    host, _, port = hostport.partition(":")
    port = int(port or 80)

    # Rebuild the request in origin form, stripping proxy-only headers.
    out_headers = []
    for line in headers:
        k = line.split(b":", 1)[0].strip().lower()
        if k in (b"proxy-authorization", b"proxy-connection"):
            continue
        out_headers.append(line)
    _head, _, body = raw.partition(b"\r\n\r\n")
    request = (
        f"{method} {path} {version}\r\n".encode()
        + b"\r\n".join(out_headers)
        + b"\r\n\r\n"
        + body
    )

    try:
        upstream = socket.create_connection((host, port), timeout=15)
    except OSError as e:
        client.sendall(f"HTTP/1.1 502 Bad Gateway\r\n\r\n{e}".encode())
        return
    upstream.sendall(request)
    pipe(client, upstream)
    upstream.close()


def handle_client(client, addr):
    try:
        raw = recv_headers(client)
        if not raw:
            return
        method, target, version, headers, raw = parse_request(raw)

        if not authorized(headers):
            send_407(client)
            return

        if method.upper() == "CONNECT":
            handle_connect(client, target)
        else:
            handle_plain(client, method, target, version, headers, raw)
    except Exception as e:  # keep one bad request from crashing the server
        sys.stderr.write(f"[error] {addr}: {e}\n")
    finally:
        client.close()


def main():
    if PASS == "change-me-please":
        sys.stderr.write(
            "\n!!  WARNING: using the default password. Set PROXY_PASS before\n"
            "    exposing this to the internet:  PROXY_PASS=secret python3 web.py\n\n"
        )
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((HOST, PORT))
    server.listen(128)
    print(f"Proxy listening on {HOST}:{PORT}")
    print(f"Auth: user='{USER}'  pass='{PASS}'")
    print(f"Expose it with:  ngrok tcp {PORT}")
    print("Press Ctrl+C to stop.\n")
    try:
        while True:
            client, addr = server.accept()
            threading.Thread(
                target=handle_client, args=(client, addr), daemon=True
            ).start()
    except KeyboardInterrupt:
        print("\nShutting down.")
    finally:
        server.close()


if __name__ == "__main__":
    main()

client.py

client side
client.py
# run this part on the client side

#!/usr/bin/env python3
"""
client.py — run this on the OTHER device.

It launches a fresh browser window whose traffic is routed through your Mac's
proxy (web.py, exposed via `ngrok tcp`). Every page that browser loads is
fetched by your Mac, using your Mac's internet connection / IP address.

Fill in the four values below with what your Mac printed:
  - the ngrok host + port  (from `ngrok tcp 8080`, e.g. 7.tcp.ngrok.io 18123)
  - the proxy username + password (from web.py)

Then run:   python3 client.py
"""

import os
import shutil
import subprocess
import sys
import tempfile

# --- fill these in -----------------------------------------------------------
PROXY_HOST = "7.tcp.ngrok.io"   # <-- from your `ngrok tcp 8080` output
PROXY_PORT = 18123              # <-- from your `ngrok tcp 8080` output
PROXY_USER = "username"            # <-- must match web.py
PROXY_PASS = "change-me-please" # <-- must match web.py
START_URL = "https://whatismyipaddress.com"  # opens here so you can confirm
# -----------------------------------------------------------------------------


def find_chrome():
    """Locate a Chromium-based browser on whatever OS this client runs on
    (macOS / Windows / Linux). This is the *client* device's browser."""
    if sys.platform == "darwin":           # macOS
        candidates = [
            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
            "/Applications/Chromium.app/Contents/MacOS/Chromium",
            "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
            "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
        ]
    elif sys.platform == "win32":          # Windows
        progfiles = [
            os.environ.get("PROGRAMFILES", r"C:\Program Files"),
            os.environ.get("PROGRAMFILES(X86)", r"C:\Program Files (x86)"),
            os.environ.get("LOCALAPPDATA", ""),
        ]
        rels = [
            r"Google\Chrome\Application\chrome.exe",
            r"Microsoft\Edge\Application\msedge.exe",
            r"BraveSoftware\Brave-Browser\Application\brave.exe",
            r"Chromium\Application\chrome.exe",
        ]
        candidates = [os.path.join(b, r) for b in progfiles if b for r in rels]
    else:                                  # Linux / other
        candidates = []

    for path in candidates:
        if os.path.exists(path):
            return path

    # Fall back to anything on PATH (covers Linux and PATH-installed browsers).
    for name in ("google-chrome", "google-chrome-stable", "chromium",
                 "chromium-browser", "microsoft-edge", "msedge",
                 "brave-browser", "brave", "chrome"):
        found = shutil.which(name)
        if found:
            return found
    return None


def main():
    chrome = find_chrome()
    if not chrome:
        sys.exit(
            "No Chromium-based browser found. Install Google Chrome, Chromium, "
            "Edge, or Brave (Chrome handles proxy auth most smoothly)."
        )

    proxy_server = f"{PROXY_HOST}:{PROXY_PORT}"

    # Use a throwaway profile so we don't touch the user's normal browser data,
    # and so the proxy applies cleanly to this window only.
    profile_dir = tempfile.mkdtemp(prefix="mac-proxy-")

    args = [
        chrome,
        f"--proxy-server=http://{proxy_server}",
        # never bypass the proxy — force *all* traffic through the Mac:
        "--proxy-bypass-list=<-loopback>",
        f"--user-data-dir={profile_dir}",
        "--no-first-run",
        "--no-default-browser-check",
        "--new-window",
        START_URL,
    ]

    print(f"Routing browser through Mac proxy at {proxy_server}")
    print("When the browser prompts for a proxy username/password, enter:")
    print(f"  username: {PROXY_USER}")
    print(f"  password: {PROXY_PASS}")
    print("\nLaunching browser...")
    subprocess.Popen(args)


if __name__ == "__main__":
    main()