Add HttpUtil, refactor the network connection function

This commit is contained in:
2dust
2025-03-18 09:55:40 +08:00
parent 55a11bbeee
commit 1972f83b86
7 changed files with 142 additions and 133 deletions

View File

@@ -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()
""

View File

@@ -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()
""

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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<Long, String> {
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

View File

@@ -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)