upnpproxy

package
v0.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package upnpproxy contains the HTTP byte proxy that fronts an upstream UPnP MediaServer's `<res>` URL with bit-exact passthrough.

**Why a standalone package.** Two surfaces serve track bytes today and both need the proxy:

  1. `internal/api`'s authed `/v1/download` path serves iOS clients; UPnP-routed tracks proxy via this package from the `serveFile` fast-path.
  2. `internal/dlna`'s unauth `/dlna/file/{trackID}` path serves DLNA renderers (the bridge's own MediaServer; LAN-only bind); UPnP-routed tracks also need to proxy here so iOS can cast a 2Go track to any DLNA renderer via the bridge. Pre-this-pkg `/dlna/file/{trackID}` 404'd on UPnP-routed tracks because the handler only knew the filesystem resolver — exposed by the post-pair-A operator verification of PR #732.

Moving the proxy here avoids the `internal/dlna` → `internal/api` import cycle (api already depends on dlna for the file handler wiring); both surfaces import this package as equals.

**Range / If-Range / If-Modified-Since flow through unchanged**; the upstream's status / Content-Type / Content-Length / Content-Range / Accept-Ranges flow back unchanged. This is the bit-exact contract — the bridge serves the 2Go's bytes verbatim.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HostResolver

type HostResolver interface {
	LiveHost(udn string) (hostport string, ok bool)
}

HostResolver returns the live `host:port` for a given UPnP server UDN — the host part of the upstream's URL floats with DHCP, Wi-Fi / hotspot toggles, etc. Production wiring is the SSDP discovery cache. Returns ("", false) when the server isn't currently reachable; callers surface 503 so iOS reconciles on the next play tap.

type PreStreamError

type PreStreamError struct {
	Status  int
	Code    string
	Message string
	Cause   error
}

PreStreamError is returned from `Proxy.Serve` when the proxy failed BEFORE any response headers were written — the caller is then expected to write its preferred error envelope shape:

  • `internal/api` uses a structured JSON envelope so iOS can decode typed errors;
  • `internal/dlna` uses `http.Error` plain-text so DLNA renderers get the HTTP status they expect.

Mid-stream failures (the upstream hangs up after sending response headers) are logged internally and `Serve` returns nil — there's no way to relay a clean error after WriteHeader has gone out.

func (*PreStreamError) Error

func (e *PreStreamError) Error() string

func (*PreStreamError) Unwrap

func (e *PreStreamError) Unwrap() error

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy holds the configuration shared across consumers. Constructed once at startup and shared.

func New

func New(hostResolver HostResolver, log *slog.Logger) *Proxy

New constructs a Proxy with the default streaming-tuned HTTP client. Pass `nil` for the logger to drop mid-stream copy errors silently (the response status is already on the wire, so there's nothing user-actionable to surface).

func (*Proxy) Serve

func (p *Proxy) Serve(ctx context.Context, w http.ResponseWriter, method string, header http.Header, rt *manifest.UPnPRouting) *PreStreamError

Serve fetches the upstream MediaServer's bytes for the given routing row + forwards them to `w` with bit-exact passthrough. `Range` / `If-Range` / `If-Modified-Since` headers from the request flow through unchanged; the upstream's status / Content-Type / Content-Length / Content-Range / Accept-Ranges flow back unchanged.

Returns `nil` when the response is already on the wire (success OR a mid-stream failure that was logged) — the caller MUST NOT touch `w` further. Returns a non-nil `*PreStreamError` when the failure happened before any headers were written; the caller writes its preferred error envelope.

`ctx` carries client-side cancellation so a scrubber drag / app background tears the upstream connection down too.

type RoutingLookup

type RoutingLookup interface {
	GetUPnPRouting(ctx context.Context, sourcePath string) (*manifest.UPnPRouting, error)
}

RoutingLookup is the manifest-side query for "is this manifest path actually a UPnP-sourced track?". Production wiring passes a `*manifest.Store` (which implements GetUPnPRouting); tests pass an in-memory stub. Returning (nil, nil) is the explicit "this is a filesystem track, not UPnP" signal — the proxy then doesn't engage.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL