|
|
|
|
@ -79,23 +79,18 @@ object HttpClient {
|
|
|
|
|
class CustomDns : Dns {
|
|
|
|
|
@Throws(UnknownHostException::class)
|
|
|
|
|
override fun lookup(hostname: String): List<InetAddress> {
|
|
|
|
|
try {
|
|
|
|
|
return try {
|
|
|
|
|
// 优先使用系统默认 DNS
|
|
|
|
|
return Dns.SYSTEM.lookup(hostname)
|
|
|
|
|
Dns.SYSTEM.lookup(hostname)
|
|
|
|
|
} catch (e: UnknownHostException) {
|
|
|
|
|
val dnsServers: List<String> = mutableListOf("8.8.8.8", "8.8.4.4")
|
|
|
|
|
val addresses = ArrayList<InetAddress>()
|
|
|
|
|
// 如果系统 DNS 失败,尝试直接使用 InetAddress 解析
|
|
|
|
|
// 这可能会使用系统配置的其他 DNS 服务器
|
|
|
|
|
try {
|
|
|
|
|
dnsServers.forEach { dnsServer ->
|
|
|
|
|
val resolved = InetAddress.getAllByName(dnsServer)
|
|
|
|
|
addresses.addAll(listOf(*resolved))
|
|
|
|
|
}
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
InetAddress.getAllByName(hostname).toList()
|
|
|
|
|
} catch (fallbackException: UnknownHostException) {
|
|
|
|
|
// 如果还是失败,抛出原始异常
|
|
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
if (addresses.isNotEmpty()) {
|
|
|
|
|
return addresses
|
|
|
|
|
}
|
|
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -161,157 +156,206 @@ object HttpClient {
|
|
|
|
|
class HttpClientException(val code: Int, message: String) : Exception(message)
|
|
|
|
|
|
|
|
|
|
suspend fun Request.call(): Response {
|
|
|
|
|
val request = this
|
|
|
|
|
val response = Response(startTime = System.currentTimeMillis())
|
|
|
|
|
LogUtils.info("call request: ${request.toJsonString()}")
|
|
|
|
|
var responseCode = 0
|
|
|
|
|
var responseData: ByteArray
|
|
|
|
|
LogUtils.info("call request: ${this.toJsonString()}")
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
val url = try {
|
|
|
|
|
URL(request.url)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED,
|
|
|
|
|
e.message ?: ""
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val url = parseUrl()
|
|
|
|
|
val encryptKey = url.host.toByteArray()
|
|
|
|
|
updateUserAgentFromHeaders()
|
|
|
|
|
val okRequest = buildOkHttpRequest(url, encryptKey)
|
|
|
|
|
val okResponse = executeRequest(okRequest)
|
|
|
|
|
processResponse(response, okResponse, encryptKey, url)
|
|
|
|
|
} catch (e: HttpClientException) {
|
|
|
|
|
handleHttpClientException(response, e)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
handleGenericException(response, e)
|
|
|
|
|
} finally {
|
|
|
|
|
response.endTime = System.currentTimeMillis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userAgent = request.headers.filter {
|
|
|
|
|
it.key.contentEquals(
|
|
|
|
|
"User-Agent",
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
}.values.firstOrNull() ?: systemUserAgent
|
|
|
|
|
|
|
|
|
|
val okHeaderBuilder = Headers.Builder()
|
|
|
|
|
request.headers.forEach { okHeaderBuilder.add(it.key, it.value) }
|
|
|
|
|
val okHeader = okHeaderBuilder.build()
|
|
|
|
|
LogUtils.info("system user agent $userAgent")
|
|
|
|
|
|
|
|
|
|
val requestBuilder = okhttp3.Request.Builder()
|
|
|
|
|
.url(request.url)
|
|
|
|
|
.headers(okHeader)
|
|
|
|
|
|
|
|
|
|
val okRequest = when (method) {
|
|
|
|
|
HttpMethod.Get -> requestBuilder.get().build()
|
|
|
|
|
HttpMethod.Post -> {
|
|
|
|
|
val enBody = if (body.isNotEmpty() && request.isEncryptRequest()) {
|
|
|
|
|
encryptKey.encryption(Encrypt(body.compress()))
|
|
|
|
|
} else {
|
|
|
|
|
body
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val contentType = headers.filter {
|
|
|
|
|
it.key.contentEquals(
|
|
|
|
|
"content-type",
|
|
|
|
|
ignoreCase = true
|
|
|
|
|
)
|
|
|
|
|
}.values.firstOrNull() ?: "application/x-www-form-urlencoded; charset=utf-8"
|
|
|
|
|
val requestBody = when (contentType.lowercase()) {
|
|
|
|
|
"multipart/form-data" -> {
|
|
|
|
|
val kvParams = String(body).split("&")
|
|
|
|
|
val kvMap = kvParams.map { val kv = it.split("="); kv[0] to kv[1] }
|
|
|
|
|
|
|
|
|
|
val multipartBodyBuilder =
|
|
|
|
|
Builder(boundary = generateBoundaryString())
|
|
|
|
|
.setType(MultipartBody.FORM)
|
|
|
|
|
|
|
|
|
|
kvMap.forEach {
|
|
|
|
|
multipartBodyBuilder.addFormDataPart(
|
|
|
|
|
it.first.UrlDecode(),
|
|
|
|
|
it.second.UrlDecode()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
multipartBodyBuilder.build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
if (request.isEncryptRequest()) {
|
|
|
|
|
enBody.toRequestBody(REQUEST_HEADER_CONTENT_TYPE_STREAM.toMediaTypeOrNull())
|
|
|
|
|
} else {
|
|
|
|
|
enBody.toRequestBody(contentType.toMediaTypeOrNull())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requestBuilder
|
|
|
|
|
.post(requestBody)
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
LogUtils.info("got response ${response.toJsonString()}")
|
|
|
|
|
return response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.parseUrl(): URL {
|
|
|
|
|
return try {
|
|
|
|
|
URL(url)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED,
|
|
|
|
|
e.message ?: "Invalid URL: $url"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.updateUserAgentFromHeaders() {
|
|
|
|
|
userAgent = headers
|
|
|
|
|
.filter { it.key.contentEquals("User-Agent", true) }
|
|
|
|
|
.values
|
|
|
|
|
.firstOrNull() ?: systemUserAgent
|
|
|
|
|
LogUtils.info("system user agent $userAgent")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.buildOkHttpRequest(url: URL, encryptKey: ByteArray): okhttp3.Request {
|
|
|
|
|
val okHeaders = buildHeaders()
|
|
|
|
|
val requestBuilder = okhttp3.Request.Builder()
|
|
|
|
|
.url(this.url)
|
|
|
|
|
.headers(okHeaders)
|
|
|
|
|
|
|
|
|
|
return when (method) {
|
|
|
|
|
HttpMethod.Get -> requestBuilder.get().build()
|
|
|
|
|
HttpMethod.Post -> buildPostRequest(requestBuilder, encryptKey)
|
|
|
|
|
null -> throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_NO_SUCH_METHOD,
|
|
|
|
|
"Not supported the type of request: ${method?.value}"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.buildHeaders(): Headers {
|
|
|
|
|
val builder = Headers.Builder()
|
|
|
|
|
headers.forEach { builder.add(it.key, it.value) }
|
|
|
|
|
return builder.build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.buildPostRequest(
|
|
|
|
|
requestBuilder: okhttp3.Request.Builder,
|
|
|
|
|
encryptKey: ByteArray
|
|
|
|
|
): okhttp3.Request {
|
|
|
|
|
val processedBody = processRequestBody(encryptKey)
|
|
|
|
|
val contentType = getContentType()
|
|
|
|
|
val requestBody = buildRequestBody(processedBody, contentType)
|
|
|
|
|
|
|
|
|
|
return requestBuilder.post(requestBody).build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.processRequestBody(encryptKey: ByteArray): ByteArray {
|
|
|
|
|
return if (body.isNotEmpty() && isEncryptRequest()) {
|
|
|
|
|
encryptKey.encryption(Encrypt(body.compress()))
|
|
|
|
|
} else {
|
|
|
|
|
body
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.getContentType(): String {
|
|
|
|
|
return headers
|
|
|
|
|
.filter { it.key.contentEquals("content-type", ignoreCase = true) }
|
|
|
|
|
.values
|
|
|
|
|
.firstOrNull() ?: "application/x-www-form-urlencoded; charset=utf-8"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
null -> throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_NO_SUCH_METHOD,
|
|
|
|
|
"Not supported the type of request: ${method?.value}"
|
|
|
|
|
private fun Request.buildRequestBody(
|
|
|
|
|
body: ByteArray,
|
|
|
|
|
contentType: String
|
|
|
|
|
): okhttp3.RequestBody {
|
|
|
|
|
return when (contentType.lowercase()) {
|
|
|
|
|
"multipart/form-data" -> buildMultipartBody(body)
|
|
|
|
|
else -> buildRegularRequestBody(body, contentType)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.buildMultipartBody(body: ByteArray): okhttp3.RequestBody {
|
|
|
|
|
val kvParams = String(body).split("&")
|
|
|
|
|
val kvMap = kvParams.mapNotNull { param ->
|
|
|
|
|
val parts = param.split("=", limit = 2)
|
|
|
|
|
if (parts.size == 2) parts[0] to parts[1] else null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val builder = Builder(boundary = generateBoundaryString())
|
|
|
|
|
.setType(MultipartBody.FORM)
|
|
|
|
|
|
|
|
|
|
kvMap.forEach { (key, value) ->
|
|
|
|
|
builder.addFormDataPart(key.urlDecode(), value.urlDecode())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return builder.build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Request.buildRegularRequestBody(
|
|
|
|
|
body: ByteArray,
|
|
|
|
|
contentType: String
|
|
|
|
|
): okhttp3.RequestBody {
|
|
|
|
|
val mediaType = if (isEncryptRequest()) {
|
|
|
|
|
REQUEST_HEADER_CONTENT_TYPE_STREAM.toMediaTypeOrNull()
|
|
|
|
|
} else {
|
|
|
|
|
contentType.toMediaTypeOrNull()
|
|
|
|
|
}
|
|
|
|
|
return body.toRequestBody(mediaType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun executeRequest(request: okhttp3.Request): okhttp3.Response {
|
|
|
|
|
return try {
|
|
|
|
|
val response = okHttpClient.awaitCall(request)
|
|
|
|
|
val pool = okHttpClient.connectionPool
|
|
|
|
|
LogUtils.info("alive connection: ${pool.connectionCount()}")
|
|
|
|
|
response
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
when (e) {
|
|
|
|
|
is UnknownHostException -> throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_UNKNOWN_HOST,
|
|
|
|
|
e.toString()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
// val requestHeader = okRequest.headers.toMultimap().map{ "\"${it.key}\":\"${it.value.joinToString(",")}\"" }
|
|
|
|
|
// LogUtils.info("real request header: ${requestHeader}")
|
|
|
|
|
val okResponse = okHttpClient
|
|
|
|
|
.awaitCall(okRequest)
|
|
|
|
|
responseCode = okResponse.code
|
|
|
|
|
response.headers = okResponse.headers.toMultimap()
|
|
|
|
|
responseData = okResponse.body.bytes()
|
|
|
|
|
LogUtils.info("${System.currentTimeMillis()} end call http request $url")
|
|
|
|
|
if (isEncryptRequest()) {
|
|
|
|
|
responseData = encryptKey.encryption(Decrypt(responseData)).unCompress()
|
|
|
|
|
LogUtils.info("call url $url received encrypt data ${String(responseData)}")
|
|
|
|
|
}
|
|
|
|
|
okResponse.close()
|
|
|
|
|
val pool = okHttpClient.connectionPool
|
|
|
|
|
LogUtils.info("alive connection: ${pool.connectionCount()}")
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
when (e) {
|
|
|
|
|
is UnknownHostException -> {
|
|
|
|
|
throw HttpClientException(ERROR_CODE_HTTP_UNKNOWN_HOST, e.toString())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is ConnectException -> {
|
|
|
|
|
throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_ESTABLISH_CONNECTION_FAILED,
|
|
|
|
|
e.toString()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is SocketTimeoutException -> {
|
|
|
|
|
throw HttpClientException(TIME_OUT, e.toString())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
LogUtils.info("exception: $e")
|
|
|
|
|
throw Exception(e)
|
|
|
|
|
}
|
|
|
|
|
is ConnectException -> throw HttpClientException(
|
|
|
|
|
ERROR_CODE_HTTP_ESTABLISH_CONNECTION_FAILED,
|
|
|
|
|
e.toString()
|
|
|
|
|
)
|
|
|
|
|
is SocketTimeoutException -> throw HttpClientException(
|
|
|
|
|
TIME_OUT,
|
|
|
|
|
e.toString()
|
|
|
|
|
)
|
|
|
|
|
else -> {
|
|
|
|
|
LogUtils.info("exception: $e")
|
|
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
response.code = responseCode
|
|
|
|
|
response.data = responseData
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (e: HttpClientException) {
|
|
|
|
|
response.code = e.code
|
|
|
|
|
response.data = e.toString().toByteArray()
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
LogUtils.info("1exception: $e")
|
|
|
|
|
response.code = ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED
|
|
|
|
|
response.data = e.toString().toByteArray()
|
|
|
|
|
private fun Request.processResponse(
|
|
|
|
|
response: Response,
|
|
|
|
|
okResponse: okhttp3.Response,
|
|
|
|
|
encryptKey: ByteArray,
|
|
|
|
|
url: URL
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
response.code = okResponse.code
|
|
|
|
|
response.headers = okResponse.headers.toMultimap()
|
|
|
|
|
var responseData = okResponse.body.bytes()
|
|
|
|
|
|
|
|
|
|
LogUtils.info("${System.currentTimeMillis()} end call http request $url")
|
|
|
|
|
|
|
|
|
|
if (isEncryptRequest()) {
|
|
|
|
|
responseData = encryptKey.encryption(Decrypt(responseData)).unCompress()
|
|
|
|
|
LogUtils.info("call url $url received encrypt data ${String(responseData)}")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response.data = responseData
|
|
|
|
|
} finally {
|
|
|
|
|
response.endTime = System.currentTimeMillis()
|
|
|
|
|
okResponse.close()
|
|
|
|
|
}
|
|
|
|
|
LogUtils.info("got response ${response.toJsonString()}")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun handleHttpClientException(response: Response, e: HttpClientException) {
|
|
|
|
|
response.code = e.code
|
|
|
|
|
response.data = e.toString().toByteArray()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
private fun handleGenericException(response: Response, e: Exception) {
|
|
|
|
|
LogUtils.info("exception: $e")
|
|
|
|
|
response.code = ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED
|
|
|
|
|
response.data = e.toString().toByteArray()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun String.UrlDecode(): String = URLDecoder.decode(this, "UTF-8")
|
|
|
|
|
private fun String.urlDecode(): String = URLDecoder.decode(this, "UTF-8")
|
|
|
|
|
|
|
|
|
|
private fun generateBoundaryString(length: Int = 16): String {
|
|
|
|
|
val chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray()
|
|
|
|
|
chars.shuffle()
|
|
|
|
|
val char16 = chars.slice(0 until 16).joinToString("")
|
|
|
|
|
return "----WebKitFormBoundary$char16"
|
|
|
|
|
val chars = (('a'..'z') + ('A'..'Z') + ('0'..'9')).toList()
|
|
|
|
|
val randomChars = chars
|
|
|
|
|
.shuffled()
|
|
|
|
|
.take(length)
|
|
|
|
|
.joinToString("")
|
|
|
|
|
return "----WebKitFormBoundary$randomChars"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun OkHttpClient.awaitCall(request: okhttp3.Request): okhttp3.Response =
|
|
|
|
|
|