Proxy Plex through stemserve (/plex/) so HTTPS pages avoid mixed-content blocks

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Sonnet 5.5 committed 2026-10-20 13:11:18 -07:00
1 parent 4b53a720c4
commit 7ca9d0374d
2 files changed
+33 -3

No files matched your search

+30
View File
@@ -16,6 +16,7 @@ Endpoints (all GET unless noted; add ?token=... or X-Stem-Token when TOKEN is se
POST /api/prepare?key=<key>&fmt=opus&br=160 start transcodes in the background
POST /api/stem {"paths": [...]} "Stem Now": stemd separates these Plex files next
/file/<key>/<file>[?fmt=opus&br=160] the stem itself, with Range support
/plex/... proxy to PLEX_URL (needs TOKEN or the Plex token)
/api/config Plex URL and token from PLEX_URL / PLEX_TOKEN, if set
/ the web player (web/, or WEB_DIR); no token needed
@@ -33,7 +34,9 @@ import subprocess
import sys
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from array import array
from concurrent.futures import ThreadPoolExecutor
from http import HTTPStatus
@@ -263,6 +266,31 @@ class Handler(BaseHTTPRequestHandler):
return True
return TOKEN in (self.headers.get("X-Stem-Token"), query.get("token", [None])[0])
def _plex_proxy(self, path, rawquery, query):
"""Forward to PLEX_URL so an HTTPS page can reach an HTTP Plex. Needs the stem or Plex token."""
given = self.headers.get("X-Plex-Token") or query.get("X-Plex-Token", [None])[0]
if not (self._authorized(query) or (PLEX_TOKEN and given == PLEX_TOKEN)):
return self._error(HTTPStatus.UNAUTHORIZED, "bad token")
pairs = [(k, v) for k, v in urllib.parse.parse_qsl(rawquery, keep_blank_values=True) if k not in ("token", "X-Plex-Token")]
req = urllib.request.Request(PLEX_URL + path + ("?" + urllib.parse.urlencode(pairs) if pairs else ""))
req.add_header("X-Plex-Token", PLEX_TOKEN or given or "")
for h in ("Range", "Accept", "X-Plex-Client-Identifier", "X-Plex-Product", "If-None-Match"):
if self.headers.get(h):
req.add_header(h, self.headers[h])
try:
r = urllib.request.urlopen(req, timeout=30)
except urllib.error.HTTPError as e:
r = e
except OSError as e:
return self._error(502, f"plex unreachable: {e}")
self.send_response(r.status if hasattr(r, "status") else r.code)
for h in ("Content-Type", "Content-Length", "Content-Range", "Accept-Ranges", "ETag", "Cache-Control"):
if r.headers.get(h):
self.send_header(h, r.headers[h])
self.end_headers()
while chunk := r.read(65536):
self.wfile.write(chunk)
def _send(self, status, body=b"", ctype="application/json", headers=None):
self.send_response(status)
self.send_header("Content-Type", ctype)
@@ -299,6 +327,8 @@ class Handler(BaseHTTPRequestHandler):
url = urllib.parse.urlsplit(self.path)
query = urllib.parse.parse_qs(url.query)
path = urllib.parse.unquote(url.path)
if path.startswith("/plex/") and PLEX_URL:
return self._plex_proxy(path[len("/plex"):], url.query, query)
if not path.startswith(("/api/", "/file/")):
return self._web(path)
if not self._authorized(query):