Skip to content

Commit 3ccf778

Browse files
committed
feat(proxy): add interceptor with proxy support
1 parent b5dd0f1 commit 3ccf778

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.lagradost.cloudstream3.network
2+
3+
import android.util.Log
4+
import okhttp3.Credentials
5+
import okhttp3.Interceptor
6+
import okhttp3.OkHttpClient
7+
import okhttp3.Response
8+
import java.io.IOException
9+
import java.net.ConnectException
10+
import java.net.InetSocketAddress
11+
import java.net.Proxy
12+
import java.net.SocketTimeoutException
13+
import java.util.concurrent.TimeUnit
14+
15+
class ProxyInterceptor(
16+
private val host: String,
17+
private val port: Int,
18+
private val proxyType: Proxy.Type = Proxy.Type.HTTP,
19+
private val username: String? = null,
20+
private val password: String? = null,
21+
private val allowFallback: Boolean = false,
22+
private val connectTimeoutSeconds: Long = 15L,
23+
private val readTimeoutSeconds: Long = 15L
24+
) : Interceptor {
25+
26+
companion object {
27+
private const val TAG = "ProxyDebug"
28+
}
29+
30+
init {
31+
Log.d(
32+
TAG,
33+
"proxy setup: " + listOf(
34+
"host=$host",
35+
"port=$port",
36+
"type=${proxyType.name}",
37+
"timeouts=${connectTimeoutSeconds}s/${readTimeoutSeconds}s",
38+
"auth=${if (username != null) "enabled" else "None"}",
39+
"fallback=${if (allowFallback) "Allowed" else "Disabled"}"
40+
).joinToString(separator = ", ") // Join only the parameters
41+
)
42+
}
43+
44+
private val proxyClient by lazy {
45+
Log.d(TAG, "Building proxy client for $host:$port")
46+
47+
val proxy = Proxy(proxyType, InetSocketAddress(host, port))
48+
OkHttpClient.Builder()
49+
.proxy(proxy)
50+
.connectTimeout(connectTimeoutSeconds, TimeUnit.SECONDS)
51+
.readTimeout(readTimeoutSeconds, TimeUnit.SECONDS)
52+
.apply {
53+
if (username != null && password != null) {
54+
Log.d(TAG, "Configuring proxy credentials")
55+
proxyAuthenticator { _, response ->
56+
Log.d(TAG, "Authenticating proxy for ${response.request.url}")
57+
response.request.newBuilder()
58+
.header("Proxy-Authorization", Credentials.basic(username, password))
59+
.build()
60+
}
61+
}
62+
}
63+
.build()
64+
}
65+
66+
override fun intercept(chain: Interceptor.Chain): Response {
67+
Log.d(TAG, "Intercepting request to ${chain.request().url.host}")
68+
69+
return try {
70+
val response = proxyClient.newCall(chain.request()).execute()
71+
72+
Log.d(
73+
TAG,
74+
"proxy response:" + listOf(
75+
"url=${response.request.url}",
76+
"status=${response.code}",
77+
"headers=${response.headers.size}",
78+
"body=${response.body?.contentLength() ?: 0} bytes"
79+
).joinToString(separator = " , ")
80+
)
81+
82+
when {
83+
response.code == 407 -> handleProxyAuthenticationError(chain, response)
84+
!response.isSuccessful -> throw IOException("HTTP ${response.code}")
85+
else -> response
86+
}
87+
} catch (e: IOException) {
88+
Log.d(
89+
TAG,
90+
"proxy error:" + listOf(
91+
"type=${e.javaClass.simpleName}",
92+
"message=${e.message}",
93+
"request=${chain.request().url}"
94+
).joinToString(separator = " , ")
95+
)
96+
handleProxyError(e, chain)
97+
}
98+
}
99+
100+
private fun handleProxyAuthenticationError(
101+
chain: Interceptor.Chain,
102+
response: Response
103+
): Response {
104+
response.close()
105+
Log.d(TAG, "Proxy authentication failed for $host:$port")
106+
return if (allowFallback) {
107+
Log.d(TAG, "Attempting fallback connection")
108+
fallback(chain)
109+
} else {
110+
throw IOException("Proxy authentication required")
111+
}
112+
}
113+
114+
private fun handleProxyError(e: IOException, chain: Interceptor.Chain): Response {
115+
return when (e) {
116+
is ConnectException -> {
117+
Log.d(TAG, "Connection refused to proxy $host:$port")
118+
if (allowFallback) fallback(chain) else throw e
119+
}
120+
121+
is SocketTimeoutException -> {
122+
Log.d(TAG, "Timeout connecting to proxy (${connectTimeoutSeconds}s)")
123+
if (allowFallback) fallback(chain) else throw e
124+
}
125+
126+
else -> {
127+
Log.d(TAG, "Unexpected proxy error: ${e.javaClass.simpleName}")
128+
throw e
129+
}
130+
}
131+
}
132+
133+
private fun fallback(chain: Interceptor.Chain): Response {
134+
Log.d(TAG, "Using direct connection to ${chain.request().url.host}")
135+
return chain.proceed(chain.request()).also { response ->
136+
Log.d(
137+
TAG,
138+
"direct connection: " + listOf(
139+
"status=${response.code}",
140+
"via=${response.handshake?.tlsVersion ?: "Plaintext"}",
141+
"server=${response.header("Server") ?: "Unknown"}"
142+
).joinToString(separator = " , ")
143+
)
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)