working proxy without any features
This commit is contained in:
commit
44157290cc
55
client.py
Normal file
55
client.py
Normal file
@ -0,0 +1,55 @@
|
||||
import subprocess
|
||||
|
||||
from httpx import Request, Response, HTTPTransport, AsyncHTTPTransport
|
||||
|
||||
def resolve_host(host, dns_server="8.8.8.8") -> list[str]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["dig", "+short", host, f"@{dns_server}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
ips = result.stdout.strip().split("\n")
|
||||
return [ip for ip in ips if ip]
|
||||
except subprocess.CalledProcessError:
|
||||
return []
|
||||
|
||||
class NameSolver:
|
||||
def get(self, name: str) -> str:
|
||||
if name == 'clients.plex.tv':
|
||||
hosts = resolve_host(name)
|
||||
if hosts:
|
||||
return hosts[0]
|
||||
|
||||
return ''
|
||||
|
||||
def resolve(self, request: Request) -> Request:
|
||||
host = request.url.host
|
||||
ip = self.get(host)
|
||||
|
||||
if ip:
|
||||
request.extensions["sni_hostname"] = host
|
||||
request.url = request.url.copy_with(host=ip)
|
||||
|
||||
return request
|
||||
|
||||
|
||||
class CustomHost(HTTPTransport):
|
||||
def __init__(self, solver: NameSolver, *args, **kwargs) -> None:
|
||||
self.solver = solver
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def handle_request(self, request: Request) -> Response:
|
||||
request = self.solver.resolve(request)
|
||||
return super().handle_request(request)
|
||||
|
||||
|
||||
class AsyncCustomHost(AsyncHTTPTransport):
|
||||
def __init__(self, solver: NameSolver, *args, **kwargs) -> None:
|
||||
self.solver = solver
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def handle_async_request(self, request: Request) -> Response:
|
||||
request = self.solver.resolve(request)
|
||||
return await super().handle_async_request(request)
|
83
main_app.py
Normal file
83
main_app.py
Normal file
@ -0,0 +1,83 @@
|
||||
import httpx
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import Response
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from client import AsyncCustomHost, NameSolver
|
||||
from const import OFFICIAL_API
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Configure upstream client
|
||||
official_client = httpx.AsyncClient(
|
||||
verify=True,
|
||||
timeout=httpx.Timeout(30.0, connect=10.0),
|
||||
limits=httpx.Limits(max_keepalive_connections=50, max_connections=100),
|
||||
transport=AsyncCustomHost(NameSolver())
|
||||
)
|
||||
|
||||
# Hop-by-hop headers we must strip
|
||||
HOP_BY_HOP_HEADERS = {
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"proxy-authenticate",
|
||||
"proxy-authorization",
|
||||
"te",
|
||||
"trailer",
|
||||
"transfer-encoding",
|
||||
"upgrade",
|
||||
"content-length", # let httpx calculate
|
||||
"host", # must be rewritten
|
||||
}
|
||||
|
||||
|
||||
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def catch_all(request: Request, path: str):
|
||||
# Copy request body
|
||||
body = await request.body()
|
||||
|
||||
# Copy headers, filtering hop-by-hop
|
||||
req_headers = {
|
||||
k: v
|
||||
for k, v in request.headers.items()
|
||||
if k.lower() not in HOP_BY_HOP_HEADERS
|
||||
}
|
||||
|
||||
# Rewrite Host header to match upstream
|
||||
req_headers["host"] = httpx.URL(OFFICIAL_API).host
|
||||
|
||||
# Forward request upstream
|
||||
upstream_response = await official_client.request(
|
||||
method=request.method,
|
||||
url=f"{OFFICIAL_API}/{path}",
|
||||
headers=req_headers,
|
||||
content=body,
|
||||
params=request.query_params,
|
||||
cookies=request.cookies,
|
||||
)
|
||||
|
||||
# Copy response headers, filtering hop-by-hop
|
||||
resp_headers = {
|
||||
k: v
|
||||
for k, v in upstream_response.headers.items()
|
||||
if k.lower() not in HOP_BY_HOP_HEADERS
|
||||
}
|
||||
|
||||
content_type = upstream_response.headers.get("content-type", "")
|
||||
|
||||
resp_headers.pop("content-encoding", None) # remove, since body is decoded
|
||||
if content_type.startswith("application/json"):
|
||||
response = JSONResponse(
|
||||
content=upstream_response.json(),
|
||||
status_code=upstream_response.status_code,
|
||||
headers=resp_headers
|
||||
)
|
||||
else:
|
||||
response = Response(
|
||||
content=upstream_response.content,
|
||||
status_code=upstream_response.status_code,
|
||||
headers=resp_headers,
|
||||
media_type=content_type
|
||||
)
|
||||
|
||||
return response
|
Loading…
x
Reference in New Issue
Block a user