diff --git a/build.gradle.kts b/build.gradle.kts index 7cba5934..afaf45a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,7 +27,7 @@ plugins { } group = "io.github.udhayarajan" -version = "5.5.9.0.4" +version = "5.5.9.0.5" //Version Naming incremented if ".." //Priority on incrementing Feature > BugFix > Beta diff --git a/src/commonMain/kotlin/com/mugames/vidsnapkit/extractor/Extractor.kt b/src/commonMain/kotlin/com/mugames/vidsnapkit/extractor/Extractor.kt index c0422d22..9a2933ba 100644 --- a/src/commonMain/kotlin/com/mugames/vidsnapkit/extractor/Extractor.kt +++ b/src/commonMain/kotlin/com/mugames/vidsnapkit/extractor/Extractor.kt @@ -25,6 +25,7 @@ import com.mugames.vidsnapkit.dataholders.ProgressState import com.mugames.vidsnapkit.dataholders.Result import com.mugames.vidsnapkit.network.HttpRequest import com.mugames.vidsnapkit.sanitizeAsHeaderValue +import io.ktor.client.network.sockets.* import io.ktor.client.plugins.* import kotlinx.coroutines.* import kotlinx.coroutines.future.future @@ -158,6 +159,8 @@ abstract class Extractor( clientRequestError() else if (e is ClientRequestException && inputUrl.contains("instagram")) onProgress(Result.Failed(Error.Instagram404Error(cookies != null))) + else if (e is SocketTimeoutException) + onProgress(Result.Failed(Error.NonFatalError("socket can't connect please try again"))) else onProgress(Result.Failed(Error.InternalError("Error in SafeAnalyze", e))) } diff --git a/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequest.kt b/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequest.kt index f6f3edd4..642d51ed 100644 --- a/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequest.kt +++ b/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequest.kt @@ -19,6 +19,7 @@ package com.mugames.vidsnapkit.network import io.ktor.client.* import io.ktor.client.engine.android.* +import io.ktor.client.plugins.* import io.ktor.client.request.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -42,18 +43,22 @@ class HttpRequest( companion object { private var prefixUrl = "" private var additionHeader: Hashtable? = null - private fun defaultClient(requiresRedirection: Boolean = true) = HttpInterfaceImpl( - HttpClient(Android).config { - followRedirects = requiresRedirection - } - ) private var clientGenerator: () -> HttpClient = { HttpClient(Android) } - private fun createClient(requiresRedirection: Boolean = true): HttpInterface { - return HttpInterfaceImpl(clientGenerator().config { followRedirects = requiresRedirection }) + private fun getClient( + useCustomClient: Boolean, + requiresRedirection: Boolean = true, + ): HttpInterfaceImpl { + val httpClient = if (useCustomClient) clientGenerator() else HttpClient(Android) + return HttpInterfaceImpl(httpClient.config { + followRedirects = requiresRedirection + install(HttpTimeout) { + socketTimeoutMillis = 13_000 + } + }) } /** @@ -90,9 +95,8 @@ class HttpRequest( */ suspend fun getResponse(needsRedirection: Boolean = true, useCustomClient: Boolean = true): String? = withContext(Dispatchers.IO) { - (if (useCustomClient) createClient(needsRedirection) else defaultClient(needsRedirection)).getData( - getUrl(), - getHeader() + getClient(useCustomClient, needsRedirection).getData( + getUrl(), getHeader() ) } @@ -101,30 +105,24 @@ class HttpRequest( * * @return bytes count of given [url] */ - suspend fun getSize(useCustomClient: Boolean = true) = - (if (useCustomClient) createClient() else defaultClient()).getSize(url, getHeader()) + suspend fun getSize(useCustomClient: Boolean = true) = getClient(useCustomClient).getSize(url, getHeader()) suspend fun postRequest(postData: Hashtable? = null, useCustomClient: Boolean = true): String = withContext(Dispatchers.IO) { - (if (useCustomClient) createClient() else defaultClient()).postData( - getUrl(), - postData, - getHeader() + getClient(useCustomClient).postData( + getUrl(), postData, getHeader() ) } suspend fun getRawResponse(needsRedirection: Boolean = true, useCustomClient: Boolean = true) = - (if (useCustomClient) createClient(needsRedirection) else defaultClient(needsRedirection)).getRawResponse( - getUrl(), - getHeader() + getClient(useCustomClient, needsRedirection).getRawResponse( + getUrl(), getHeader() ) - suspend fun isAvailable(useCustomClient: Boolean = true): Boolean = - withContext(Dispatchers.IO) { - (if (useCustomClient) createClient(false) else defaultClient(false)).checkWebPage( - getUrl(), - getHeader() - ) - } + suspend fun isAvailable(useCustomClient: Boolean = true): Boolean = withContext(Dispatchers.IO) { + getClient(useCustomClient, false).checkWebPage( + getUrl(), getHeader() + ) + } } diff --git a/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequestHelper.kt b/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequestHelper.kt index e2cf4037..54186774 100644 --- a/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequestHelper.kt +++ b/src/commonMain/kotlin/com/mugames/vidsnapkit/network/HttpRequestHelper.kt @@ -19,7 +19,7 @@ package com.mugames.vidsnapkit.network import com.mugames.vidsnapkit.toJsonString import io.ktor.client.* -import io.ktor.client.call.* +import io.ktor.client.network.sockets.* import io.ktor.client.plugins.* import io.ktor.client.request.* import io.ktor.client.statement.* @@ -67,7 +67,7 @@ class HttpInterfaceImpl( headers: Hashtable?, ): String { return try { - client.post { + val data = client.post { url(url) headers?.let { if (it.isNotEmpty()) @@ -80,11 +80,14 @@ class HttpInterfaceImpl( setBody(TextContent(it.toJsonString(), ContentType.Application.Json)) } }.bodyAsText() + client.close() + data } catch (e: Exception) { logger.error( "postData() url=${url} header=${headers.toString()} & postData=${postData.toString()} Error:", e ) + client.close() throw e } } @@ -116,22 +119,27 @@ class HttpInterfaceImpl( } } }.run { - status in acceptedStatusCode || run { + val data = status in acceptedStatusCode || run { if (status in redirectionStatusCode) { val res = getLastPossibleRedirectedResponse(this, headers) val isPageAvailable = res.status in acceptedStatusCode || res.status in redirectionStatusCode logger.info("page availability = $isPageAvailable") + client.close() return isPageAvailable } logger.warn("Unhandled in checkWebPage() status code=${status} for url=${url} with headers=${headers.toString()} & response=${bodyAsText()}") false } + client.close() + data } } catch (e: ClientRequestException) { logger.error("checkWebPage() url=${url} header=${headers.toString()} ClientRequestException:", e) + client.close() false } catch (e: Exception) { logger.error("checkWebPage() url=${url} header=${headers.toString()} GenericException:", e) + client.close() false } } @@ -149,38 +157,50 @@ class HttpInterfaceImpl( } } }.run { - if (status == HttpStatusCode.OK) - body() - else if (status in redirectionStatusCode) { - getLastPossibleRedirectedResponse(this, headers).body() - } else if (url.contains("instagram") && status == HttpStatusCode.InternalServerError) "{error:\"Invalid Cookies\"}" - else if (status == HttpStatusCode.TooManyRequests) { + if (status == HttpStatusCode.OK) { + val data = bodyAsText() + client.close() + data + } else if (status in redirectionStatusCode) { + val data = getLastPossibleRedirectedResponse(this, headers).bodyAsText() + client.close() + data + } else if (url.contains("instagram") && status == HttpStatusCode.InternalServerError) { + client.close() + "{error:\"Invalid Cookies\"}" + } else if (status == HttpStatusCode.TooManyRequests) { logger.warn("Unhandled in getData() TooManyRequest for url=${url} with headers=${headers.toString()} & response=${bodyAsText()}") + client.close() "429" } else { logger.warn("Unhandled in getData() status code=${status} for url=${url} with headers=${headers.toString()} &\n response=${bodyAsText()}") + client.close() null } } } catch (e: ClientRequestException) { logger.error("getData() url=${url} header=${headers.toString()} ClientRequestException:", e) + client.close() null } catch (e: SendCountExceedException) { - if (url.contains("instagram") && headers?.containsKey("Cookie") == true) + if (url.contains("instagram") && headers?.containsKey("Cookie") == true) { + client.close() "{error:\"Invalid Cookies\"}" - else { + } else { logger.error("getData() url=${url} header=${headers.toString()} SendCountExceedException:", e) + client.close() throw e } } catch (e: Exception) { logger.error("getData() url=${url} header=${headers.toString()} Generic exception:", e) + client.close() throw e } } override suspend fun getRawResponse(url: String, headers: Hashtable?): HttpResponse? { return try { - client.get { + val response = client.get { url(url) headers?.let { if (it.isNotEmpty()) { @@ -191,11 +211,12 @@ class HttpInterfaceImpl( } } } + client.close() + response } catch (e: Exception) { - var x = false - client.config { x = followRedirects } + client.close() logger.error( - "getRawResponse() url=${url} header=${headers.toString()} clientRedirection=${x} Generic exception:", + "getRawResponse() url=${url} header=${headers.toString()} Generic exception:", e ) null @@ -203,13 +224,32 @@ class HttpInterfaceImpl( } override suspend fun getSize(url: String, headers: Hashtable?): Long { - return client.request { - method = HttpMethod.Head - url(url) - }.run { - if (status == HttpStatusCode.OK) - this.headers["content-length"]?.toLong() ?: Long.MIN_VALUE - else Long.MIN_VALUE + return try { + client.request { + method = HttpMethod.Head + url(url) + timeout { + socketTimeoutMillis = 13_000 + requestTimeoutMillis = 13_000 + connectTimeoutMillis = 13_000 + } + }.run { + if (status == HttpStatusCode.OK) { + val data = this.headers["content-length"]?.toLong() ?: Long.MIN_VALUE + client.close() + data + } else Long.MIN_VALUE + } + } catch (e: Exception) { + client.close() + when (e) { + is HttpRequestTimeoutException, is ConnectTimeoutException, is SocketTimeoutException -> { + // handle the exception + 1 + } + + else -> throw e + } } } @@ -238,6 +278,7 @@ class HttpInterfaceImpl( } } } + nonRedirectingClient.close() if (cacheResponse.request.url == tempResponse.request.url) break cacheResponse = tempResponse