diff --git a/lib/src/main/java/com/example/http/HttpClient.kt b/lib/src/main/java/com/example/http/HttpClient.kt index e1c42d6..9ff83c8 100644 --- a/lib/src/main/java/com/example/http/HttpClient.kt +++ b/lib/src/main/java/com/example/http/HttpClient.kt @@ -79,23 +79,18 @@ object HttpClient { class CustomDns : Dns { @Throws(UnknownHostException::class) override fun lookup(hostname: String): List { - try { + return try { // 优先使用系统默认 DNS - return Dns.SYSTEM.lookup(hostname) + Dns.SYSTEM.lookup(hostname) } catch (e: UnknownHostException) { - val dnsServers: List = mutableListOf("8.8.8.8", "8.8.4.4") - val addresses = ArrayList() + // 如果系统 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 =