Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow HTTP redirect in remote discovery #929

3 changes: 2 additions & 1 deletion jellyfin-api-ktor/api/jellyfin-api-ktor.api
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ public class org/jellyfin/sdk/api/ktor/KtorClient : org/jellyfin/sdk/api/client/
public fun getDeviceInfo ()Lorg/jellyfin/sdk/model/DeviceInfo;
public fun getHttpClientOptions ()Lorg/jellyfin/sdk/api/client/HttpClientOptions;
public fun getWebSocket ()Lorg/jellyfin/sdk/api/sockets/SocketApi;
public fun request (Lorg/jellyfin/sdk/api/client/HttpMethod;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun headRequest (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Lkotlin/ranges/IntRange;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun request (Lorg/jellyfin/sdk/api/client/HttpMethod;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;Lkotlin/ranges/IntRange;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun setAccessToken (Ljava/lang/String;)V
public fun setBaseUrl (Ljava/lang/String;)V
public fun setClientInfo (Lorg/jellyfin/sdk/model/ClientInfo;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jellyfin.sdk.api.ktor

import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.HeadResponse
import org.jellyfin.sdk.api.client.HttpClientOptions
import org.jellyfin.sdk.api.client.HttpMethod
import org.jellyfin.sdk.api.client.RawResponse
Expand All @@ -23,5 +24,13 @@ public expect open class KtorClient(
pathParameters: Map<String, Any?>,
queryParameters: Map<String, Any?>,
requestBody: Any?,
expectedResponseCodes: IntRange,
): RawResponse

public override suspend fun headRequest(
pathTemplate: String,
pathParameters: Map<String, Any?>,
queryParameters: Map<String, Any?>,
expectedResponseCodes: IntRange,
): HeadResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import io.ktor.content.ByteArrayContent
import io.ktor.content.TextContent
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.isSuccess
import io.ktor.util.toMap
import kotlinx.serialization.SerializationException
import mu.KotlinLogging
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.HeadResponse
import org.jellyfin.sdk.api.client.HttpClientOptions
import org.jellyfin.sdk.api.client.HttpMethod
import org.jellyfin.sdk.api.client.RawResponse
Expand Down Expand Up @@ -84,26 +83,53 @@ public actual open class KtorClient actual constructor(
}
}

@Suppress("ThrowsCount")
public actual override suspend fun request(
method: HttpMethod,
pathTemplate: String,
pathParameters: Map<String, Any?>,
queryParameters: Map<String, Any?>,
requestBody: Any?,
expectedResponseCodes: IntRange,
): RawResponse {
val url = createUrl(pathTemplate, pathParameters, queryParameters)
return generalRequest(
method = method.asKtorHttpMethod(),
url = createUrl(pathTemplate, pathParameters, queryParameters),
requestBody = requestBody,
expectedResponseCodes = 200 until 300,
)
}

public actual override suspend fun headRequest(
pathTemplate: String,
pathParameters: Map<String, Any?>,
queryParameters: Map<String, Any?>,
expectedResponseCodes: IntRange,
): HeadResponse {
return generalRequest(
method = KtorHttpMethod.Head,
url = createUrl(pathTemplate, pathParameters, queryParameters),
requestBody = null,
expectedResponseCodes = expectedResponseCodes,
).createHeadResponse()
}

@Suppress("ThrowsCount")
private suspend fun generalRequest(
method: KtorHttpMethod,
url: String,
requestBody: Any?,
expectedResponseCodes: IntRange,
) : RawResponse {
// Log HTTP call with access token removed
val logger = KotlinLogging.logger {}
logger.info {
val safeUrl = accessToken?.let { url.replace(it, "******") } ?: url
"$method $safeUrl"
"${method.value} $safeUrl"
}

try {
val response = client.request(url) {
this.method = method.asKtorHttpMethod()
this.method = method

header(
key = HttpHeaders.Accept,
Expand Down Expand Up @@ -134,9 +160,9 @@ public actual open class KtorClient actual constructor(
}

// Check HTTP status
if (!response.status.isSuccess()) throw InvalidStatusException(response.status.value)
if (response.status.value !in expectedResponseCodes) throw InvalidStatusException(response.status.value)
// Return custom response instance
return RawResponse(response.bodyAsChannel(), response.status.value, response.headers.toMap())
return RawResponse(response.bodyAsChannel(), response.status.value, response.headers)
} catch (err: UnknownHostException) {
logger.debug(err) { "HTTP host unreachable" }
throw TimeoutException("HTTP host unreachable", err)
Expand Down
17 changes: 13 additions & 4 deletions jellyfin-api/api/jellyfin-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ public abstract class org/jellyfin/sdk/api/client/ApiClient {
public abstract fun getHttpClientOptions ()Lorg/jellyfin/sdk/api/client/HttpClientOptions;
public final fun getOrCreateApi (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lorg/jellyfin/sdk/api/operations/Api;
public abstract fun getWebSocket ()Lorg/jellyfin/sdk/api/sockets/SocketApi;
public abstract fun request (Lorg/jellyfin/sdk/api/client/HttpMethod;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun request$default (Lorg/jellyfin/sdk/api/client/ApiClient;Lorg/jellyfin/sdk/api/client/HttpMethod;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun headRequest (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Lkotlin/ranges/IntRange;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun headRequest$default (Lorg/jellyfin/sdk/api/client/ApiClient;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Lkotlin/ranges/IntRange;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun request (Lorg/jellyfin/sdk/api/client/HttpMethod;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;Lkotlin/ranges/IntRange;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun request$default (Lorg/jellyfin/sdk/api/client/ApiClient;Lorg/jellyfin/sdk/api/client/HttpMethod;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;Lkotlin/ranges/IntRange;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun setAccessToken (Ljava/lang/String;)V
public abstract fun setBaseUrl (Ljava/lang/String;)V
public abstract fun setClientInfo (Lorg/jellyfin/sdk/model/ClientInfo;)V
Expand All @@ -25,6 +27,12 @@ public abstract class org/jellyfin/sdk/api/client/ApiClient {
public final class org/jellyfin/sdk/api/client/ApiClient$Companion {
}

public final class org/jellyfin/sdk/api/client/HeadResponse {
public fun <init> (ILio/ktor/http/Headers;)V
public final fun getHeaders ()Lio/ktor/http/Headers;
public final fun getStatus ()I
}

public final class org/jellyfin/sdk/api/client/HttpClientOptions {
public synthetic fun <init> (ZJJJLorg/jellyfin/sdk/api/sockets/SocketReconnectPolicy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (ZJJJLorg/jellyfin/sdk/api/sockets/SocketReconnectPolicy;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down Expand Up @@ -55,9 +63,10 @@ public final class org/jellyfin/sdk/api/client/HttpMethod : java/lang/Enum {
}

public final class org/jellyfin/sdk/api/client/RawResponse {
public fun <init> (Lio/ktor/utils/io/ByteReadChannel;ILjava/util/Map;)V
public fun <init> (Lio/ktor/utils/io/ByteReadChannel;ILio/ktor/http/Headers;)V
public final fun createHeadResponse ()Lorg/jellyfin/sdk/api/client/HeadResponse;
public final fun getBody ()Lio/ktor/utils/io/ByteReadChannel;
public final fun getHeaders ()Ljava/util/Map;
public final fun getHeaders ()Lio/ktor/http/Headers;
public final fun getStatus ()I
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,16 @@ public abstract class ApiClient {
pathParameters: Map<String, Any?> = emptyMap(),
queryParameters: Map<String, Any?> = emptyMap(),
requestBody: Any? = null,
expectedResponseCodes: IntRange = 200 until 300
): RawResponse

public abstract suspend fun headRequest(
Maxr1998 marked this conversation as resolved.
Show resolved Hide resolved
pathTemplate: String = "",
pathParameters: Map<String, Any?> = emptyMap(),
queryParameters: Map<String, Any?> = emptyMap(),
expectedResponseCodes: IntRange = 300 until 400
): HeadResponse

/**
* Get the instance of the SocketApi for this ApiClient.
* @see SocketApi
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.jellyfin.sdk.api.client

import io.ktor.http.Headers

public class HeadResponse(
public val status: Int,
public val headers: Headers
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jellyfin.sdk.api.client

import io.ktor.http.Headers
import io.ktor.util.toMap
import io.ktor.utils.io.ByteReadChannel
import kotlinx.serialization.SerializationException
import mu.KotlinLogging
Expand All @@ -9,7 +11,7 @@ import org.jellyfin.sdk.api.client.util.ApiSerializer
public class RawResponse(
public val body: ByteReadChannel,
public val status: Int,
public val headers: Map<String, List<String>>,
public val headers: Headers,
) {
public suspend inline fun <reified T : Any> createContent(): T {
val logger = KotlinLogging.logger {}
Expand All @@ -23,5 +25,8 @@ public class RawResponse(
}

public suspend inline fun <reified T : Any> createResponse(): Response<T> =
Response(createContent(), status, headers)
Response(createContent(), status, headers.toMap())

public fun createHeadResponse(): HeadResponse =
HeadResponse(status, headers)
}
30 changes: 24 additions & 6 deletions jellyfin-core/api/android/jellyfin-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ public final class org/jellyfin/sdk/discovery/DiscoveryService {
public static synthetic fun discoverLocalServers$default (Lorg/jellyfin/sdk/discovery/DiscoveryService;IIILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public final fun getAddressCandidates (Ljava/lang/String;)Ljava/util/Collection;
public final fun getRecommendedServers (Ljava/lang/String;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getRecommendedServers (Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getRecommendedServers (Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun getRecommendedServers$default (Lorg/jellyfin/sdk/discovery/DiscoveryService;Ljava/lang/String;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static synthetic fun getRecommendedServers$default (Lorg/jellyfin/sdk/discovery/DiscoveryService;Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static synthetic fun getRecommendedServers$default (Lorg/jellyfin/sdk/discovery/DiscoveryService;Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public final class org/jellyfin/sdk/discovery/LocalServerDiscovery {
Expand All @@ -152,26 +152,31 @@ public final class org/jellyfin/sdk/discovery/LocalServerDiscovery$Companion {

public final class org/jellyfin/sdk/discovery/RecommendedServerDiscovery {
public fun <init> (Lorg/jellyfin/sdk/Jellyfin;)V
public final fun discover (Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun discover (Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun discover$default (Lorg/jellyfin/sdk/discovery/RecommendedServerDiscovery;Ljava/util/Collection;Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public final class org/jellyfin/sdk/discovery/RecommendedServerInfo {
public fun <init> (Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Ljava/lang/Object;)V
public fun <init> (Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Ljava/lang/Object;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Ljava/lang/Object;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()J
public final fun component3 ()Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;
public final fun component4 ()Ljava/util/Collection;
public final fun component5-d1pmJ48 ()Ljava/lang/Object;
public final fun copy (Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Ljava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerInfo;
public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerInfo;Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Lkotlin/Result;ILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerInfo;
public final fun component6 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Ljava/lang/Object;Ljava/lang/String;)Lorg/jellyfin/sdk/discovery/RecommendedServerInfo;
public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerInfo;Ljava/lang/String;JLorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;Ljava/util/Collection;Lkotlin/Result;Ljava/lang/String;ILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerInfo;
public fun equals (Ljava/lang/Object;)Z
public final fun firstIssueOrNull ()Lorg/jellyfin/sdk/discovery/RecommendedServerIssue;
public final fun getAddress ()Ljava/lang/String;
public final fun getIssues ()Ljava/util/Collection;
public final fun getOriginalAddress ()Ljava/lang/String;
public final fun getResponseTime ()J
public final fun getScore ()Lorg/jellyfin/sdk/discovery/RecommendedServerInfoScore;
public final fun getSystemInfo-d1pmJ48 ()Ljava/lang/Object;
public fun hashCode ()I
public final fun isRedirect ()Z
public fun toString ()Ljava/lang/String;
}

Expand Down Expand Up @@ -228,6 +233,19 @@ public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$OutdatedSer
public fun toString ()Ljava/lang/String;
}

public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$RedirectedResponse : org/jellyfin/sdk/discovery/RecommendedServerIssue {
public fun <init> ()V
public fun <init> (Z)V
public synthetic fun <init> (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun copy (Z)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$RedirectedResponse;
public static synthetic fun copy$default (Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$RedirectedResponse;ZILjava/lang/Object;)Lorg/jellyfin/sdk/discovery/RecommendedServerIssue$RedirectedResponse;
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
public final fun isRedirect ()Z
public fun toString ()Ljava/lang/String;
}

public final class org/jellyfin/sdk/discovery/RecommendedServerIssue$SecureConnectionFailed : org/jellyfin/sdk/discovery/RecommendedServerIssue {
public fun <init> (Lorg/jellyfin/sdk/api/client/exception/SecureConnectionException;)V
public final fun component1 ()Lorg/jellyfin/sdk/api/client/exception/SecureConnectionException;
Expand Down
Loading
Loading