From 1972f83b868e7ead9681f14cdbf1fa150404ec19 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 18 Mar 2025 09:55:40 +0800 Subject: [PATCH] Add HttpUtil, refactor the network connection function --- .../com/v2ray/ang/handler/AngConfigManager.kt | 7 +- .../java/com/v2ray/ang/ui/MainActivity.kt | 5 +- .../com/v2ray/ang/ui/PerAppProxyActivity.kt | 3 +- .../com/v2ray/ang/ui/UserAssetActivity.kt | 25 +--- .../main/java/com/v2ray/ang/util/HttpUtil.kt | 126 ++++++++++++++++++ .../java/com/v2ray/ang/util/SpeedtestUtil.kt | 21 +-- .../src/main/java/com/v2ray/ang/util/Utils.kt | 88 ------------ 7 files changed, 142 insertions(+), 133 deletions(-) create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt index 32b19f00..865a0817 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt @@ -16,6 +16,7 @@ import com.v2ray.ang.fmt.TrojanFmt import com.v2ray.ang.fmt.VlessFmt import com.v2ray.ang.fmt.VmessFmt import com.v2ray.ang.fmt.WireguardFmt +import com.v2ray.ang.util.HttpUtil import com.v2ray.ang.util.JsonUtil import com.v2ray.ang.util.QRCodeDecoder import com.v2ray.ang.util.Utils @@ -346,7 +347,7 @@ object AngConfigManager { if (!it.second.enabled) { return 0 } - val url = Utils.idnToASCII(it.second.url) + val url = HttpUtil.idnToASCII(it.second.url) if (!Utils.isValidUrl(url)) { return 0 } @@ -354,7 +355,7 @@ object AngConfigManager { var configText = try { val httpPort = SettingsManager.getHttpPort() - Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort) + HttpUtil.getUrlContentWithUserAgent(url, 30000, httpPort) } catch (e: Exception) { Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error, try……") //e.printStackTrace() @@ -362,7 +363,7 @@ object AngConfigManager { } if (configText.isEmpty()) { configText = try { - Utils.getUrlContentWithCustomUserAgent(url) + HttpUtil.getUrlContentWithUserAgent(url) } catch (e: Exception) { e.printStackTrace() "" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt index 54b6a520..f9fdce8e 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt @@ -1,7 +1,6 @@ package com.v2ray.ang.ui import android.Manifest -import android.content.ActivityNotFoundException import android.content.Intent import android.content.pm.PackageManager import android.content.res.ColorStateList @@ -39,6 +38,7 @@ import com.v2ray.ang.handler.MigrateManager import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.service.V2RayServiceManager +import com.v2ray.ang.util.HttpUtil import com.v2ray.ang.util.Utils import com.v2ray.ang.viewmodel.MainViewModel import kotlinx.coroutines.Dispatchers @@ -46,7 +46,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.drakeet.support.toast.ToastCompat -import java.util.concurrent.TimeUnit class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { private val binding by lazy { @@ -626,7 +625,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } lifecycleScope.launch(Dispatchers.IO) { val configText = try { - Utils.getUrlContentWithCustomUserAgent(url) + HttpUtil.getUrlContentWithUserAgent(url) } catch (e: Exception) { e.printStackTrace() "" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt index 10e1a10f..a6fdfdef 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.text.Collator +import com.v2ray.ang.util.HttpUtil class PerAppProxyActivity : BaseActivity() { private val binding by lazy { @@ -154,7 +155,7 @@ class PerAppProxyActivity : BaseActivity() { toast(R.string.msg_downloading_content) val url = AppConfig.androidpackagenamelistUrl lifecycleScope.launch(Dispatchers.IO) { - val content = Utils.getUrlContext(url, 5000) + val content = HttpUtil.getUrlContent(url, 5000) launch(Dispatchers.Main) { Log.d(ANG_PACKAGE, content) selectProxyApp(content, true) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt index 06d371e6..ae197a60 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.v2ray.ang.AppConfig -import com.v2ray.ang.AppConfig.LOOPBACK import com.v2ray.ang.R import com.v2ray.ang.databinding.ActivitySubSettingBinding import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding @@ -30,6 +29,7 @@ import com.v2ray.ang.extension.toTrafficString import com.v2ray.ang.extension.toast import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.SettingsManager +import com.v2ray.ang.util.HttpUtil import com.v2ray.ang.util.Utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -37,9 +37,6 @@ import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.net.HttpURLConnection -import java.net.InetSocketAddress -import java.net.Proxy -import java.net.URL import java.text.DateFormat import java.util.Date @@ -186,8 +183,10 @@ class UserAssetActivity : BaseActivity() { return false } // Send URL to UserAssetUrlActivity for Processing - startActivity(Intent(this, UserAssetUrlActivity::class.java) - .putExtra(UserAssetUrlActivity.ASSET_URL_QRCODE, url)) + startActivity( + Intent(this, UserAssetUrlActivity::class.java) + .putExtra(UserAssetUrlActivity.ASSET_URL_QRCODE, url) + ) } catch (e: Exception) { e.printStackTrace() return false @@ -229,22 +228,10 @@ class UserAssetActivity : BaseActivity() { private fun downloadGeo(item: AssetUrlItem, timeout: Int, httpPort: Int): Boolean { val targetTemp = File(extDir, item.remarks + "_temp") val target = File(extDir, item.remarks) - var conn: HttpURLConnection? = null //Log.d(AppConfig.ANG_PACKAGE, url) + val conn = HttpUtil.createProxyConnection(item.url, httpPort, timeout, timeout, needStream = true) ?: return false try { - conn = if (httpPort == 0) { - URL(item.url).openConnection() as HttpURLConnection - } else { - URL(item.url).openConnection( - Proxy( - Proxy.Type.HTTP, - InetSocketAddress(LOOPBACK, httpPort) - ) - ) as HttpURLConnection - } - conn.connectTimeout = timeout - conn.readTimeout = timeout val inputStream = conn.inputStream val responseCode = conn.responseCode if (responseCode == HttpURLConnection.HTTP_OK) { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt new file mode 100644 index 00000000..d3d41458 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt @@ -0,0 +1,126 @@ +package com.v2ray.ang.util + +import com.v2ray.ang.AppConfig.LOOPBACK +import com.v2ray.ang.BuildConfig +import com.v2ray.ang.util.Utils.encode +import com.v2ray.ang.util.Utils.urlDecode +import java.io.IOException +import java.net.* +import java.util.* + +object HttpUtil { + + fun idnToASCII(str: String): String { + val url = URL(str) + return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file).toExternalForm() + } + + fun getUrlContent(url: String, timeout: Int): String { + var result: String = "" + val conn = createProxyConnection(url, 0, timeout, timeout) ?: return result + try { + result = conn.inputStream.bufferedReader().readText() + } catch (_: Exception) { + } finally { + conn.disconnect() + } + return result + } + + @Throws(IOException::class) + fun getUrlContentWithUserAgent(url: String?, timeout: Int = 30000, httpPort: Int = 0): String { + var currentUrl = url + var redirects = 0 + val maxRedirects = 3 + + while (redirects++ < maxRedirects) { + if (currentUrl == null) continue + val conn = createProxyConnection(currentUrl, httpPort, timeout, timeout) ?: continue + conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}") + conn.connect() + + val responseCode = conn.responseCode + when (responseCode) { + in 300..399 -> { + val location = conn.getHeaderField("Location") + conn.disconnect() + if (location.isNullOrEmpty()) { + throw IOException("Redirect location not found") + } + currentUrl = location + continue + } + + else -> try { + return conn.inputStream.use { it.bufferedReader().readText() } + } finally { + conn.disconnect() + } + } + } + throw IOException("Too many redirects") + } + + /** + * Creates an HttpURLConnection object connected through a proxy. + * + * @param urlStr The target URL address. + * @param ip The IP address of the proxy server. + * @param port The port of the proxy server. + * @param connectTimeout The connection timeout in milliseconds (default is 30000 ms). + * @param readTimeout The read timeout in milliseconds (default is 30000 ms). + * @param needStream + * @return Returns a configured HttpURLConnection object, or null if it fails. + */ + fun createProxyConnection( + urlStr: String, + port: Int, + connectTimeout: Int = 30000, + readTimeout: Int = 30000, + needStream: Boolean = false + ): HttpURLConnection? { + + var conn: HttpURLConnection? = null + try { + val url = URL(urlStr) + // Create a connection + conn = if (port == 0) { + url.openConnection() + } else { + url.openConnection( + Proxy( + Proxy.Type.HTTP, + InetSocketAddress(LOOPBACK, port) + ) + ) + } as HttpURLConnection + + // Set connection and read timeouts + conn.connectTimeout = connectTimeout + conn.readTimeout = readTimeout + if (!needStream) { + // Set request headers + conn.setRequestProperty("Connection", "close") + // Disable automatic redirects + conn.instanceFollowRedirects = false + // Disable caching + conn.useCaches = false + } + + //Add Basic Authorization + url.userInfo?.let { + conn.setRequestProperty( + "Authorization", + "Basic ${encode(urlDecode(it))}" + ) + } + } catch (e: Exception) { + e.printStackTrace() + // If an exception occurs, close the connection and return null + conn?.disconnect() + return null + } + return conn + } +} + diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/SpeedtestUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/SpeedtestUtil.kt index 10e793be..d1b13d21 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/SpeedtestUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/SpeedtestUtil.kt @@ -5,17 +5,14 @@ import android.os.SystemClock import android.text.TextUtils import android.util.Log import com.v2ray.ang.AppConfig -import com.v2ray.ang.AppConfig.LOOPBACK import com.v2ray.ang.R import com.v2ray.ang.extension.responseLength +import com.v2ray.ang.util.HttpUtil import kotlinx.coroutines.isActive import libv2ray.Libv2ray import java.io.IOException -import java.net.HttpURLConnection import java.net.InetSocketAddress -import java.net.Proxy import java.net.Socket -import java.net.URL import java.net.UnknownHostException import kotlin.coroutines.coroutineContext @@ -101,23 +98,9 @@ object SpeedtestUtil { fun testConnection(context: Context, port: Int): Pair { var result: String var elapsed = -1L - var conn: HttpURLConnection? = null + val conn = HttpUtil.createProxyConnection(Utils.getDelayTestUrl(), port, 30000, 30000) ?: return Pair(elapsed, "") try { - val url = URL(Utils.getDelayTestUrl()) - - conn = url.openConnection( - Proxy( - Proxy.Type.HTTP, - InetSocketAddress(LOOPBACK, port) - ) - ) as HttpURLConnection - conn.connectTimeout = 30000 - conn.readTimeout = 30000 - conn.setRequestProperty("Connection", "close") - conn.instanceFollowRedirects = false - conn.useCaches = false - val start = SystemClock.elapsedRealtime() val code = conn.responseCode elapsed = SystemClock.elapsedRealtime() - start diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt index 9f951df0..73a70840 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Utils.kt @@ -21,7 +21,6 @@ import androidx.core.content.ContextCompat import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.LOOPBACK -import com.v2ray.ang.BuildConfig import com.v2ray.ang.R import com.v2ray.ang.dto.Language import com.v2ray.ang.extension.toast @@ -337,87 +336,6 @@ object Utils { return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE)) } - fun getUrlContext(url: String, timeout: Int): String { - var result: String - var conn: HttpURLConnection? = null - - try { - conn = URL(url).openConnection() as HttpURLConnection - conn.connectTimeout = timeout - conn.readTimeout = timeout - conn.setRequestProperty("Connection", "close") - conn.instanceFollowRedirects = false - conn.useCaches = false - //val code = conn.responseCode - result = conn.inputStream.bufferedReader().readText() - } catch (e: Exception) { - result = "" - } finally { - conn?.disconnect() - } - return result - } - - @Throws(IOException::class) - fun getUrlContentWithCustomUserAgent( - urlStr: String?, - timeout: Int = 30000, - httpPort: Int = 0 - ): String { - var currentUrl = urlStr - var redirects = 0 - val maxRedirects = 5 - - while (redirects < maxRedirects) { - val url = URL(currentUrl) - val conn = if (httpPort == 0) { - url.openConnection() - } else { - url.openConnection( - Proxy( - Proxy.Type.HTTP, - InetSocketAddress(LOOPBACK, httpPort) - ) - ) - } as HttpURLConnection - - conn.connectTimeout = timeout - conn.readTimeout = timeout - conn.setRequestProperty("Connection", "close") - conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}") - url.userInfo?.let { - conn.setRequestProperty( - "Authorization", - "Basic ${encode(urlDecode(it))}" - ) - } - conn.useCaches = false - conn.instanceFollowRedirects = false - - conn.connect() - - val responseCode = conn.responseCode - when (responseCode) { - in 300..399 -> { - val location = conn.getHeaderField("Location") - conn.disconnect() - if (location.isNullOrEmpty()) { - throw IOException("Redirect location not found") - } - currentUrl = location - redirects++ - continue - } - else -> try { - return conn.inputStream.use { it.bufferedReader().readText() } - } finally { - conn.disconnect() - } - } - } - throw IOException("Too many redirects") - } - fun getDarkModeStatus(context: Context): Boolean { return context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK != UI_MODE_NIGHT_NO } @@ -477,12 +395,6 @@ object Utils { return str?.replace(" ", "") } - fun idnToASCII(str: String): String { - val url = URL(str) - return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file) - .toExternalForm() - } - fun isTv(context: Context): Boolean = context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)