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

main
mojo 1 month ago
parent 4ee8e763b9
commit 347465ffa0

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

Loading…
Cancel
Save