refactor: 重构 HttpClient 并修复 CustomDns 和 generateBoundaryString 的 bug

main
mojo 1 month ago
parent 4ee8e763b9
commit 347465ffa0

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

Loading…
Cancel
Save