fix: gate http fallback for hostname https, throw on non-2xx in failover
close #900
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io' show InternetAddress;
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'storage_service.dart';
|
||||
import 'plex_client.dart';
|
||||
@@ -8,7 +9,6 @@ import '../models/user_switch_response.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/connection_constants.dart';
|
||||
import '../utils/plex_http_client.dart';
|
||||
import '../utils/plex_http_exception.dart';
|
||||
|
||||
/// Redacts the middle of an IP address or hostname for safe logging.
|
||||
/// E.g. `192.168.1.50` → `192.***.***.50`, `my.server.example.com` → `my.***.***. com`.
|
||||
@@ -87,17 +87,7 @@ class PlexAuthService {
|
||||
return _http.get('$_plexApiBase/user', headers: _getCommonHeaders(authToken: authToken));
|
||||
}
|
||||
|
||||
/// Throw [PlexHttpException] if the response indicates a client/server error.
|
||||
void _checkStatus(PlexResponse response) {
|
||||
if (response.statusCode >= 400) {
|
||||
throw PlexHttpException(
|
||||
type: PlexHttpErrorType.unknown,
|
||||
statusCode: response.statusCode,
|
||||
responseData: response.data,
|
||||
message: 'HTTP ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
}
|
||||
void _checkStatus(PlexResponse response) => throwIfHttpError(response);
|
||||
|
||||
/// Verify if a plex.tv token is valid
|
||||
Future<bool> verifyToken(String authToken) async {
|
||||
@@ -303,8 +293,8 @@ class PlexServer {
|
||||
final connection = PlexConnection.fromJson(c as Map<String, dynamic>);
|
||||
connections.add(connection);
|
||||
|
||||
// Generate HTTP fallback for HTTPS connections
|
||||
if (connection.protocol == 'https') {
|
||||
if (connection.protocol == 'https' &&
|
||||
(connection.uri.contains('.plex.direct') || _isIpLiteral(connection.address))) {
|
||||
connections.add(connection.toHttpFallback());
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -650,9 +640,7 @@ class PlexServer {
|
||||
final isHttps = connection.protocol == 'https';
|
||||
addCandidate(connection, connection.uri, isPlexDirect, isHttps);
|
||||
|
||||
// For HTTPS connections, also add HTTP direct IP as fallback
|
||||
// This provides backward compatibility and fallback for cert issues
|
||||
if (isHttps) {
|
||||
if (isHttps && (isPlexDirect || _isIpLiteral(connection.address))) {
|
||||
addCandidate(connection, connection.httpDirectUrl, false, false);
|
||||
}
|
||||
}
|
||||
@@ -829,6 +817,13 @@ class PlexServer {
|
||||
return entries.first.key;
|
||||
}
|
||||
|
||||
/// True when the address is a raw IP (no hostname → no reverse proxy → HTTP
|
||||
/// fallback on an HTTPS port is safe to try).
|
||||
static bool _isIpLiteral(String address) {
|
||||
final bare = address.startsWith('[') && address.endsWith(']') ? address.substring(1, address.length - 1) : address;
|
||||
return InternetAddress.tryParse(bare) != null;
|
||||
}
|
||||
|
||||
/// Returns true if the address is known to be unreachable from external
|
||||
/// clients (IPv6 link-local or all-zeros).
|
||||
static bool _isUnreachableAddress(String address) {
|
||||
|
||||
@@ -213,7 +213,15 @@ class PlexClient {
|
||||
}) async {
|
||||
final gen = _endpointManager?.generation;
|
||||
try {
|
||||
return await _http.get(path, queryParameters: queryParameters, headers: headers, timeout: timeout, abort: abort);
|
||||
final response = await _http.get(
|
||||
path,
|
||||
queryParameters: queryParameters,
|
||||
headers: headers,
|
||||
timeout: timeout,
|
||||
abort: abort,
|
||||
);
|
||||
throwIfHttpError(response);
|
||||
return response;
|
||||
} on PlexHttpException catch (e) {
|
||||
if (!_shouldAttemptFailover(e) ||
|
||||
_failoverSwitching ||
|
||||
@@ -246,6 +254,7 @@ class PlexClient {
|
||||
timeout: timeout,
|
||||
abort: abort,
|
||||
);
|
||||
throwIfHttpError(response);
|
||||
appLogger.i('Endpoint failover retry succeeded', error: {'newEndpoint': nextBaseUrl});
|
||||
await _onEndpointChanged?.call(nextBaseUrl);
|
||||
return response;
|
||||
@@ -256,9 +265,11 @@ class PlexClient {
|
||||
}
|
||||
|
||||
bool _shouldAttemptFailover(PlexHttpException e) {
|
||||
final sc = e.statusCode;
|
||||
return e.type == PlexHttpErrorType.connectionTimeout ||
|
||||
e.type == PlexHttpErrorType.receiveTimeout ||
|
||||
e.type == PlexHttpErrorType.connectionError;
|
||||
e.type == PlexHttpErrorType.connectionError ||
|
||||
(sc != null && sc >= 500 && sc <= 599);
|
||||
}
|
||||
|
||||
/// Fetch /media/providers and parse libraries + EPG providers from the response.
|
||||
|
||||
@@ -27,6 +27,19 @@ class PlexResponse {
|
||||
PlexResponse({required this.statusCode, this.data, required this.headers});
|
||||
}
|
||||
|
||||
/// Throw [PlexHttpException] for non-2xx responses so callers don't blindly
|
||||
/// cast HTML/text error bodies to `Map<String, dynamic>`.
|
||||
void throwIfHttpError(PlexResponse r) {
|
||||
if (r.statusCode >= 400) {
|
||||
throw PlexHttpException(
|
||||
type: PlexHttpErrorType.unknown,
|
||||
statusCode: r.statusCode,
|
||||
responseData: r.data,
|
||||
message: 'HTTP ${r.statusCode}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Abort controller for cancelling in-flight HTTP requests.
|
||||
///
|
||||
/// Uses the `package:http` [AbortableRequest] mechanism so the underlying
|
||||
|
||||
Reference in New Issue
Block a user