Files
plezy/lib/utils/abortable_http_request.dart
edde746 e46b3dab7c fix(seerr): diagnose an auth proxy in front of Seerr instead of misreporting it
A Seerr instance behind forward-auth (Authelia, Authentik, Cloudflare
Access) or HTTP Basic failed setup with "No Seerr instance at … (HTTP
200)": the probe followed the proxy's redirect to its login page and
decoded the HTML as "not JSON". A live session that later hit the same
wall took the proxy's 401 as Seerr's, re-authed through the wall, failed,
and unlinked a perfectly good stored session.

Seerr's API never redirects and always answers with JSON, so a 3xx or a
non-JSON 401/403 is the proxy talking. Seerr requests no longer follow
redirects, that shape maps to a SeerrProxyException with a message that
says what to do, the probe reports it in place of "no instance", and the
client neither re-auths nor unlinks on it.

close #1877
2026-09-03 21:42:47 +02:00

64 lines
1.7 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<http.Response> sendAbortableHttpRequest(
http.Client client,
String method,
Uri uri, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
Duration? timeout,
Future<void>? abortTrigger,
String? operation,
bool followRedirects = true,
}) {
// Deliberately not `AbortController`: that type lives with the media-server
// client and throws `MediaServerHttpException`, which the tracker/Seerr
// callers of this helper must stay independent of.
final abort = Completer<void>();
void abortRequest() {
if (!abort.isCompleted) abort.complete();
}
if (abortTrigger != null) {
unawaited(abortTrigger.whenComplete(abortRequest));
}
final request = http.AbortableRequest(method, uri, abortTrigger: abort.future)..followRedirects = followRedirects;
if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
if (body != null) _setBody(request, body);
final future = client.send(request).then(http.Response.fromStream);
if (timeout == null) return future.whenComplete(abortRequest);
return future
.timeout(
timeout,
onTimeout: () {
abortRequest();
throw TimeoutException('${operation ?? '$method ${uri.path}'} timed out', timeout);
},
)
.whenComplete(abortRequest);
}
void _setBody(http.Request request, Object body) {
if (body is String) {
request.body = body;
return;
}
if (body is List<int>) {
request.bodyBytes = body;
return;
}
if (body is Map) {
request.bodyFields = body.cast<String, String>();
return;
}
throw ArgumentError('Invalid request body "$body".');
}