pull/977/head
parent
f91bc8722a
commit
a45b5844ab
@ -1,30 +1,111 @@ |
|||||||
package io.legado.app.help.http |
package io.legado.app.help.http |
||||||
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine |
import kotlinx.coroutines.suspendCancellableCoroutine |
||||||
|
import okhttp3.ConnectionSpec |
||||||
|
import okhttp3.Credentials |
||||||
|
import okhttp3.Interceptor |
||||||
|
import okhttp3.OkHttpClient |
||||||
|
import java.net.InetSocketAddress |
||||||
|
import java.net.Proxy |
||||||
|
import java.util.concurrent.ConcurrentHashMap |
||||||
|
import java.util.concurrent.TimeUnit |
||||||
import kotlin.coroutines.resume |
import kotlin.coroutines.resume |
||||||
|
|
||||||
@Suppress("unused") |
private val proxyClientCache: ConcurrentHashMap<String, OkHttpClient> by lazy { |
||||||
object HttpHelper { |
ConcurrentHashMap() |
||||||
|
} |
||||||
|
|
||||||
suspend fun ajax(params: AjaxWebView.AjaxParams): StrResponse = |
val okHttpClient: OkHttpClient by lazy { |
||||||
suspendCancellableCoroutine { block -> |
val specs = arrayListOf( |
||||||
val webView = AjaxWebView() |
ConnectionSpec.MODERN_TLS, |
||||||
block.invokeOnCancellation { |
ConnectionSpec.COMPATIBLE_TLS, |
||||||
webView.destroyWebView() |
ConnectionSpec.CLEARTEXT |
||||||
} |
) |
||||||
webView.callback = object : AjaxWebView.Callback() { |
|
||||||
override fun onResult(response: StrResponse) { |
|
||||||
|
|
||||||
if (!block.isCompleted) |
val builder = OkHttpClient.Builder() |
||||||
block.resume(response) |
.connectTimeout(15, TimeUnit.SECONDS) |
||||||
} |
.writeTimeout(15, TimeUnit.SECONDS) |
||||||
|
.readTimeout(15, TimeUnit.SECONDS) |
||||||
|
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager) |
||||||
|
.retryOnConnectionFailure(true) |
||||||
|
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier) |
||||||
|
.connectionSpecs(specs) |
||||||
|
.followRedirects(true) |
||||||
|
.followSslRedirects(true) |
||||||
|
.addInterceptor(Interceptor { chain -> |
||||||
|
val request = chain.request() |
||||||
|
.newBuilder() |
||||||
|
.addHeader("Keep-Alive", "300") |
||||||
|
.addHeader("Connection", "Keep-Alive") |
||||||
|
.addHeader("Cache-Control", "no-cache") |
||||||
|
.build() |
||||||
|
chain.proceed(request) |
||||||
|
}) |
||||||
|
|
||||||
|
builder.build() |
||||||
|
} |
||||||
|
|
||||||
override fun onError(error: Throwable) { |
/** |
||||||
if (!block.isCompleted) |
* 缓存代理okHttp |
||||||
block.cancel(error) |
*/ |
||||||
} |
fun getProxyClient(proxy: String? = null): OkHttpClient { |
||||||
|
if (proxy.isNullOrBlank()) { |
||||||
|
return okHttpClient |
||||||
|
} |
||||||
|
proxyClientCache[proxy]?.let { |
||||||
|
return it |
||||||
|
} |
||||||
|
val r = Regex("(http|socks4|socks5)://(.*):(\\d{2,5})(@.*@.*)?") |
||||||
|
val ms = r.findAll(proxy) |
||||||
|
val group = ms.first() |
||||||
|
var username = "" //代理服务器验证用户名 |
||||||
|
var password = "" //代理服务器验证密码 |
||||||
|
val type = if (group.groupValues[1] == "http") "http" else "socks" |
||||||
|
val host = group.groupValues[2] |
||||||
|
val port = group.groupValues[3].toInt() |
||||||
|
if (group.groupValues[4] != "") { |
||||||
|
username = group.groupValues[4].split("@")[1] |
||||||
|
password = group.groupValues[4].split("@")[2] |
||||||
|
} |
||||||
|
if (type != "direct" && host != "") { |
||||||
|
val builder = okHttpClient.newBuilder() |
||||||
|
if (type == "http") { |
||||||
|
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port))) |
||||||
|
} else { |
||||||
|
builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port))) |
||||||
|
} |
||||||
|
if (username != "" && password != "") { |
||||||
|
builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码 |
||||||
|
val credential: String = Credentials.basic(username, password) |
||||||
|
response.request.newBuilder() |
||||||
|
.header("Proxy-Authorization", credential) |
||||||
|
.build() |
||||||
} |
} |
||||||
webView.load(params) |
|
||||||
} |
} |
||||||
|
val proxyClient = builder.build() |
||||||
|
proxyClientCache[proxy] = proxyClient |
||||||
|
return proxyClient |
||||||
|
} |
||||||
|
return okHttpClient |
||||||
} |
} |
||||||
|
|
||||||
|
suspend fun getWebViewSrc(params: AjaxWebView.AjaxParams): StrResponse = |
||||||
|
suspendCancellableCoroutine { block -> |
||||||
|
val webView = AjaxWebView() |
||||||
|
block.invokeOnCancellation { |
||||||
|
webView.destroyWebView() |
||||||
|
} |
||||||
|
webView.callback = object : AjaxWebView.Callback() { |
||||||
|
override fun onResult(response: StrResponse) { |
||||||
|
|
||||||
|
if (!block.isCompleted) |
||||||
|
block.resume(response) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onError(error: Throwable) { |
||||||
|
if (!block.isCompleted) |
||||||
|
block.cancel(error) |
||||||
|
} |
||||||
|
} |
||||||
|
webView.load(params) |
||||||
|
} |
@ -1,187 +0,0 @@ |
|||||||
package io.legado.app.help.http |
|
||||||
|
|
||||||
import io.legado.app.constant.AppConst |
|
||||||
import io.legado.app.help.AppConfig |
|
||||||
import io.legado.app.utils.EncodingDetect |
|
||||||
import io.legado.app.utils.UTF8BOMFighter |
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine |
|
||||||
import okhttp3.* |
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl |
|
||||||
import okhttp3.MediaType.Companion.toMediaType |
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody |
|
||||||
import java.io.IOException |
|
||||||
import java.net.InetSocketAddress |
|
||||||
import java.net.Proxy |
|
||||||
import java.nio.charset.Charset |
|
||||||
import java.util.concurrent.ConcurrentHashMap |
|
||||||
import java.util.concurrent.TimeUnit |
|
||||||
import kotlin.coroutines.resume |
|
||||||
import kotlin.coroutines.resumeWithException |
|
||||||
|
|
||||||
private val proxyClientCache: ConcurrentHashMap<String, OkHttpClient> by lazy { |
|
||||||
ConcurrentHashMap() |
|
||||||
} |
|
||||||
|
|
||||||
val okHttpClient: OkHttpClient by lazy { |
|
||||||
|
|
||||||
val specs = arrayListOf( |
|
||||||
ConnectionSpec.MODERN_TLS, |
|
||||||
ConnectionSpec.COMPATIBLE_TLS, |
|
||||||
ConnectionSpec.CLEARTEXT |
|
||||||
) |
|
||||||
|
|
||||||
val builder = OkHttpClient.Builder() |
|
||||||
.connectTimeout(15, TimeUnit.SECONDS) |
|
||||||
.writeTimeout(15, TimeUnit.SECONDS) |
|
||||||
.readTimeout(15, TimeUnit.SECONDS) |
|
||||||
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager) |
|
||||||
.retryOnConnectionFailure(true) |
|
||||||
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier) |
|
||||||
.connectionSpecs(specs) |
|
||||||
.followRedirects(true) |
|
||||||
.followSslRedirects(true) |
|
||||||
.addInterceptor(Interceptor { chain -> |
|
||||||
val request = chain.request() |
|
||||||
.newBuilder() |
|
||||||
.addHeader("Keep-Alive", "300") |
|
||||||
.addHeader("Connection", "Keep-Alive") |
|
||||||
.addHeader("Cache-Control", "no-cache") |
|
||||||
.build() |
|
||||||
chain.proceed(request) |
|
||||||
}) |
|
||||||
|
|
||||||
builder.build() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 缓存代理okHttp |
|
||||||
*/ |
|
||||||
fun getProxyClient(proxy: String? = null): OkHttpClient { |
|
||||||
if (proxy.isNullOrBlank()) { |
|
||||||
return okHttpClient |
|
||||||
} |
|
||||||
proxyClientCache[proxy]?.let { |
|
||||||
return it |
|
||||||
} |
|
||||||
val r = Regex("(http|socks4|socks5)://(.*):(\\d{2,5})(@.*@.*)?") |
|
||||||
val ms = r.findAll(proxy) |
|
||||||
val group = ms.first() |
|
||||||
var username = "" //代理服务器验证用户名 |
|
||||||
var password = "" //代理服务器验证密码 |
|
||||||
val type = if (group.groupValues[1] == "http") "http" else "socks" |
|
||||||
val host = group.groupValues[2] |
|
||||||
val port = group.groupValues[3].toInt() |
|
||||||
if (group.groupValues[4] != "") { |
|
||||||
username = group.groupValues[4].split("@")[1] |
|
||||||
password = group.groupValues[4].split("@")[2] |
|
||||||
} |
|
||||||
if (type != "direct" && host != "") { |
|
||||||
val builder = okHttpClient.newBuilder() |
|
||||||
if (type == "http") { |
|
||||||
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port))) |
|
||||||
} else { |
|
||||||
builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port))) |
|
||||||
} |
|
||||||
if (username != "" && password != "") { |
|
||||||
builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码 |
|
||||||
val credential: String = Credentials.basic(username, password) |
|
||||||
response.request.newBuilder() |
|
||||||
.header("Proxy-Authorization", credential) |
|
||||||
.build() |
|
||||||
} |
|
||||||
} |
|
||||||
val proxyClient = builder.build() |
|
||||||
proxyClientCache[proxy] = proxyClient |
|
||||||
return proxyClient |
|
||||||
} |
|
||||||
return okHttpClient |
|
||||||
} |
|
||||||
|
|
||||||
suspend fun OkHttpClient.newCall(builder: Request.Builder.() -> Unit): ResponseBody { |
|
||||||
val requestBuilder = Request.Builder() |
|
||||||
requestBuilder.header(AppConst.UA_NAME, AppConfig.userAgent) |
|
||||||
requestBuilder.apply(builder) |
|
||||||
val response = this.newCall(requestBuilder.build()).await() |
|
||||||
if (!response.isSuccessful) { |
|
||||||
throw IOException("服务器没有响应。") |
|
||||||
} |
|
||||||
return response.body!! |
|
||||||
} |
|
||||||
|
|
||||||
suspend fun OkHttpClient.newCallStrResponse(builder: Request.Builder.() -> Unit): StrResponse { |
|
||||||
val requestBuilder = Request.Builder() |
|
||||||
requestBuilder.header(AppConst.UA_NAME, AppConfig.userAgent) |
|
||||||
requestBuilder.apply(builder) |
|
||||||
val response = this.newCall(requestBuilder.build()).await() |
|
||||||
if (!response.isSuccessful) { |
|
||||||
throw IOException("服务器没有响应。") |
|
||||||
} |
|
||||||
return StrResponse(response, response.body!!.text()) |
|
||||||
} |
|
||||||
|
|
||||||
suspend fun Call.await(): Response = suspendCancellableCoroutine { block -> |
|
||||||
|
|
||||||
block.invokeOnCancellation { |
|
||||||
cancel() |
|
||||||
} |
|
||||||
|
|
||||||
enqueue(object : Callback { |
|
||||||
override fun onFailure(call: Call, e: IOException) { |
|
||||||
block.resumeWithException(e) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) { |
|
||||||
block.resume(response) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
fun ResponseBody.text(encode: String? = null): String { |
|
||||||
val responseBytes = UTF8BOMFighter.removeUTF8BOM(bytes()) |
|
||||||
var charsetName: String? = encode |
|
||||||
|
|
||||||
charsetName?.let { |
|
||||||
return String(responseBytes, Charset.forName(charsetName)) |
|
||||||
} |
|
||||||
|
|
||||||
//根据http头判断 |
|
||||||
contentType()?.charset()?.let { |
|
||||||
return String(responseBytes, it) |
|
||||||
} |
|
||||||
|
|
||||||
//根据内容判断 |
|
||||||
charsetName = EncodingDetect.getHtmlEncode(responseBytes) |
|
||||||
return String(responseBytes, Charset.forName(charsetName)) |
|
||||||
} |
|
||||||
|
|
||||||
fun Request.Builder.get(url: String, queryMap: Map<String, String>, encoded: Boolean = false) { |
|
||||||
val httpBuilder = url.toHttpUrl().newBuilder() |
|
||||||
queryMap.forEach { |
|
||||||
if (encoded) { |
|
||||||
httpBuilder.addEncodedQueryParameter(it.key, it.value) |
|
||||||
} else { |
|
||||||
httpBuilder.addQueryParameter(it.key, it.value) |
|
||||||
} |
|
||||||
} |
|
||||||
url(httpBuilder.build()) |
|
||||||
} |
|
||||||
|
|
||||||
fun Request.Builder.postForm(form: Map<String, String>, encoded: Boolean = false) { |
|
||||||
val formBody = FormBody.Builder() |
|
||||||
form.forEach { |
|
||||||
if (encoded) { |
|
||||||
formBody.addEncoded(it.key, it.value) |
|
||||||
} else { |
|
||||||
formBody.add(it.key, it.value) |
|
||||||
} |
|
||||||
} |
|
||||||
post(formBody.build()) |
|
||||||
} |
|
||||||
|
|
||||||
fun Request.Builder.postJson(json: String?) { |
|
||||||
json?.let { |
|
||||||
val requestBody = json.toRequestBody("application/json; charset=UTF-8".toMediaType()) |
|
||||||
post(requestBody) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,114 @@ |
|||||||
|
package io.legado.app.help.http |
||||||
|
|
||||||
|
import io.legado.app.constant.AppConst |
||||||
|
import io.legado.app.help.AppConfig |
||||||
|
import io.legado.app.utils.EncodingDetect |
||||||
|
import io.legado.app.utils.UTF8BOMFighter |
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine |
||||||
|
import okhttp3.* |
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl |
||||||
|
import okhttp3.MediaType.Companion.toMediaType |
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody |
||||||
|
import java.io.IOException |
||||||
|
import java.nio.charset.Charset |
||||||
|
import kotlin.coroutines.resume |
||||||
|
import kotlin.coroutines.resumeWithException |
||||||
|
|
||||||
|
suspend fun OkHttpClient.newCall( |
||||||
|
retry: Int = 0, |
||||||
|
builder: Request.Builder.() -> Unit |
||||||
|
): ResponseBody { |
||||||
|
val requestBuilder = Request.Builder() |
||||||
|
requestBuilder.header(AppConst.UA_NAME, AppConfig.userAgent) |
||||||
|
requestBuilder.apply(builder) |
||||||
|
for (i in 0..retry) { |
||||||
|
val response = this.newCall(requestBuilder.build()).await() |
||||||
|
if (response.isSuccessful) { |
||||||
|
return response.body!! |
||||||
|
} |
||||||
|
} |
||||||
|
throw IOException("服务器没有响应。") |
||||||
|
} |
||||||
|
|
||||||
|
suspend fun OkHttpClient.newCallStrResponse( |
||||||
|
retry: Int = 0, |
||||||
|
builder: Request.Builder.() -> Unit |
||||||
|
): StrResponse { |
||||||
|
val requestBuilder = Request.Builder() |
||||||
|
requestBuilder.header(AppConst.UA_NAME, AppConfig.userAgent) |
||||||
|
requestBuilder.apply(builder) |
||||||
|
for (i in 0..retry) { |
||||||
|
val response = this.newCall(requestBuilder.build()).await() |
||||||
|
if (response.isSuccessful) { |
||||||
|
return StrResponse(response, response.body!!.text()) |
||||||
|
} |
||||||
|
} |
||||||
|
throw IOException("服务器没有响应。") |
||||||
|
} |
||||||
|
|
||||||
|
suspend fun Call.await(): Response = suspendCancellableCoroutine { block -> |
||||||
|
|
||||||
|
block.invokeOnCancellation { |
||||||
|
cancel() |
||||||
|
} |
||||||
|
|
||||||
|
enqueue(object : Callback { |
||||||
|
override fun onFailure(call: Call, e: IOException) { |
||||||
|
block.resumeWithException(e) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) { |
||||||
|
block.resume(response) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fun ResponseBody.text(encode: String? = null): String { |
||||||
|
val responseBytes = UTF8BOMFighter.removeUTF8BOM(bytes()) |
||||||
|
var charsetName: String? = encode |
||||||
|
|
||||||
|
charsetName?.let { |
||||||
|
return String(responseBytes, Charset.forName(charsetName)) |
||||||
|
} |
||||||
|
|
||||||
|
//根据http头判断 |
||||||
|
contentType()?.charset()?.let { |
||||||
|
return String(responseBytes, it) |
||||||
|
} |
||||||
|
|
||||||
|
//根据内容判断 |
||||||
|
charsetName = EncodingDetect.getHtmlEncode(responseBytes) |
||||||
|
return String(responseBytes, Charset.forName(charsetName)) |
||||||
|
} |
||||||
|
|
||||||
|
fun Request.Builder.get(url: String, queryMap: Map<String, String>, encoded: Boolean = false) { |
||||||
|
val httpBuilder = url.toHttpUrl().newBuilder() |
||||||
|
queryMap.forEach { |
||||||
|
if (encoded) { |
||||||
|
httpBuilder.addEncodedQueryParameter(it.key, it.value) |
||||||
|
} else { |
||||||
|
httpBuilder.addQueryParameter(it.key, it.value) |
||||||
|
} |
||||||
|
} |
||||||
|
url(httpBuilder.build()) |
||||||
|
} |
||||||
|
|
||||||
|
fun Request.Builder.postForm(form: Map<String, String>, encoded: Boolean = false) { |
||||||
|
val formBody = FormBody.Builder() |
||||||
|
form.forEach { |
||||||
|
if (encoded) { |
||||||
|
formBody.addEncoded(it.key, it.value) |
||||||
|
} else { |
||||||
|
formBody.add(it.key, it.value) |
||||||
|
} |
||||||
|
} |
||||||
|
post(formBody.build()) |
||||||
|
} |
||||||
|
|
||||||
|
fun Request.Builder.postJson(json: String?) { |
||||||
|
json?.let { |
||||||
|
val requestBody = json.toRequestBody("application/json; charset=UTF-8".toMediaType()) |
||||||
|
post(requestBody) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue