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:
- `internal/api`'s authed `/v1/download` path serves iOS clients; UPnP-routed tracks proxy via this package from the `serveFile` fast-path.
- `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 ¶
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 ¶
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.