chore: 更新相关服务类和工具类代码

main
mojo 1 month ago
parent 63ab06eefa
commit efe559c6d2

@ -4,7 +4,7 @@ import kotlin.enums.enumEntries
import kotlin.reflect.KProperty1
data class HttpActionRequest(
val url: String,
var url: String,
val method: HttpMethod,
var headers: List<NameValue> = emptyList(),
var cookies: List<NameValue> = emptyList(),

@ -1,4 +1,439 @@
package com.example.service
class HttpService {
}
import android.net.Uri
import android.util.Log
import com.example.action.BaseAction
import com.example.action.HttpActionRequest
import com.example.action.HttpMethod
import com.example.action.NameValue
import com.example.action.NameVariable
import com.example.action.Next
import com.example.http.HttpClient
import com.example.http.HttpClient.call
import com.example.http.Request
import com.example.http.Response
import com.example.logger.LogUtils
import com.example.report.ActionExec
import com.example.task.TaskConfig
import com.example.utils.toJsonString
import com.example.utils.toJsonString1
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URI
import java.net.URL
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class HttpService(
private val action: BaseAction.HttpAction,
override val taskConfig: TaskConfig
) : BaseService(taskConfig) {
companion object {
private const val MAX_REDIRECT_COUNT = 30
val pattern = Regex("^[a-zA-Z0-9]+://.*")
}
override suspend fun execute(onFinish: (List<ActionExec>) -> Unit) =
withContext(Dispatchers.Default) {
val actionExecList = mutableListOf<ActionExec>()
var currentStep = taskConfig.currentStep
try {
LogUtils.info("action delay: ${action.delay} s, it's async: ${action.async}")
if (action.delay > 0) {
delay(action.delay.toDuration(DurationUnit.SECONDS))
}
val actionRequest = action.request ?: throw NullPointerException("request is null")
amendActionRequest(actionRequest)
var httpRequest = actionRequest.buildHttpRequest()
var httpResponse: Response? = null
var proceedTask = false
var stepCallHttpCount = 1
if (action.async) {
httpRequest.makeAsyncRequest()
val actionExec = httpRequest.genActionExec(null, stepCallHttpCount)
actionExec.respCode = ASYNC_EXEC_CODE
actionExecList += actionExec
proceedTask = true
} else {
httpResponse = httpRequest.call()
val actionExec = httpRequest.genActionExec(httpResponse, stepCallHttpCount)
actionExecList += actionExec
when (httpResponse.code) {
in 100 until 300 -> {
proceedTask = true
}
in 300 until 400 -> {
var redirectUrl: String? =
httpResponse.headers.get(
HttpClient.Params.REQUEST_HEADER_LOCATION,
ignoreCase = true
)?.firstOrNull()
while ((isActive && httpResponse != null &&
httpResponse.code in (300 until 400) &&
stepCallHttpCount <= MAX_REDIRECT_COUNT &&
!redirectUrl.isNullOrBlank()) &&
redirectUrl.isHttpRedirect()
) {
runCatching {
stepCallHttpCount++
httpRequest = httpRequest.buildRedirectHttpRequest(
redirectUrl!!,
httpRequest.url
)
httpResponse = httpRequest.call()
if (httpResponse!!.code !in 300 until 400) {
if (!httpResponse?.headers.isNullOrEmpty()) {
httpResponse?.headers =
httpResponse!!.headers.toMutableMap().apply {
put(
HttpClient.Params.REQUEST_HEADER_REFERER,
listOf(
httpRequest.headers.getOrDefault(
HttpClient.Params.REQUEST_HEADER_REFERER,
ignoreCase = true,
""
)
)
)
}
}
}
redirectUrl =
httpResponse?.headers?.get(
HttpClient.Params.REQUEST_HEADER_LOCATION,
ignoreCase = true
)?.firstOrNull()
LogUtils.info("redirectUrl: $redirectUrl")
proceedTask = true
httpRequest.genActionExec(httpResponse, stepCallHttpCount)
}.onFailure { e ->
LogUtils.error(throwable = e)
proceedTask = action.skipError
actionExecList += httpRequest.genActionExec(
null, stepCallHttpCount
).apply {
respCode =
HttpClient.ErrorCode.ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED
}
}.onSuccess {
actionExecList += it
}
}
}
else -> {
proceedTask = action.skipError
}
}
}
if (proceedTask) {
httpResponse?.apply {
extractResponseVariableToCache(action, httpRequest, httpResponse)
val nextStep = action.next.httpGetNextStepIndex(
httpRequest, httpResponse, currentStep
)
taskConfig.currentStep = nextStep
} ?: let {
taskConfig.currentStep = ++currentStep
}
} else {
taskConfig.currentStep = Int.MAX_VALUE
}
} catch (e: Exception) {
LogUtils.error(throwable = e)
val actionExec = genExceptionActionExec(
action, ERROR_CODE_HTTP_ACTION_EXEC_FAILED, Log.getStackTraceString(e)
)
actionExecList += actionExec
if (action.skipError) {
taskConfig.currentStep = ++currentStep
} else {
taskConfig.currentStep = Int.MAX_VALUE
}
}
LogUtils.info("finish action: ${action.request?.url}")
onFinish(actionExecList)
}
private fun Request.genActionExec(
httpResponse: Response?, redirectCount: Int
): ActionExec {
val actionExec = ActionExec(
step = taskConfig.currentStep,
index = redirectCount,
time = System.currentTimeMillis(),
url = url,
method = method?.value ?: "GET",
reqHeader = headers.toJsonString1()
)
if (body.isNotEmpty()) {
actionExec.reqData = String(body)
}
httpResponse?.let { response ->
if (response.headers.isNotEmpty()) {
kotlin.runCatching {
URL(url).apply {
URI(protocol, host, path, query, null).let { uri ->
taskConfig.cookieManager.put(uri, response.headers)
}
}
}.onFailure {
LogUtils.error(throwable = it)
}
actionExec.respHeader = response.headers.toJsonString()
}
actionExec.respCode = response.code
if (response.data.isNotEmpty()) {
actionExec.respData = String(response.data)
}
actionExec.cost = httpResponse.endTime - httpResponse.startTime
}
return actionExec
}
private fun Request.makeAsyncRequest() = scope.launch {
var stepCallHttpCount = 0
var asyncRequest: Request = copy()
var asyncResponse = asyncRequest.call()
var locationList: MutableList<String>
var redirectUrl: String? = null
if (asyncResponse.code in 300 until 400) {
locationList =
asyncResponse.headers.get(
HttpClient.Params.REQUEST_HEADER_LOCATION,
ignoreCase = true
)?.toMutableList()
?: mutableListOf()
redirectUrl = locationList.firstOrNull()
}
while (asyncResponse.code in 300 until 400 && stepCallHttpCount <= MAX_REDIRECT_COUNT && !redirectUrl.isNullOrBlank() && redirectUrl.isHttpRedirect()) {
kotlin.runCatching {
stepCallHttpCount++
asyncRequest =
asyncRequest.buildRedirectHttpRequest(redirectUrl!!, asyncRequest.url)
asyncResponse = asyncRequest.call()
locationList =
asyncResponse.headers.get(
HttpClient.Params.REQUEST_HEADER_LOCATION,
ignoreCase = true
)?.toMutableList()
?: mutableListOf()
redirectUrl = locationList.firstOrNull()
}.onFailure {
LogUtils.error(throwable = it)
}
}
}
private fun String.isHttpRedirect(): Boolean = pattern.find(this)?.let {
return@isHttpRedirect this.startsWith("http")
} ?: true
private fun HttpActionRequest.buildHttpRequest(): Request = Request(
url = url,
method = method,
body = data.toByteArray(),
headers = headers.nameValueToMap()
)
private fun genWholeResponse(httpRequest: Request, httpResponse: Response?): String {
return """
[${httpRequest.url}]${if (httpResponse?.data?.isNotEmpty() == true) String(httpResponse.data) else ""}[${
httpResponse?.headers?.let {
if (it.isEmpty()) ""
else it.toJsonString()
}
}]
""".trimIndent()
}
private fun List<Next>.httpGetNextStepIndex(
httpRequest: Request, httpResponse: Response?, currentStep: Int
): Int {
val wholeResponse = genWholeResponse(httpRequest, httpResponse)
return getNextStepIndex(wholeResponse, currentStep)
}
private fun extractResponseVariableToCache(
action: BaseAction.HttpAction, httpRequest: Request, httpResponse: Response?
) {
action.response?.let { actionResponse ->
httpResponse?.headers?.let { responseHeaders ->
extractCookieVariableToCache(actionResponse.cookies, responseHeaders)
extractHeaderVariableToCache(actionResponse.headers, responseHeaders)
extractBodyVariableToCache(
action, genWholeResponse(httpRequest, httpResponse), httpResponse.data
)
}
}
}
private fun extractCookieVariableToCache(
cookies: List<NameVariable>, responseHeaders: Map<String, List<String>>
) {
if (cookies.isEmpty()) return
runCatching {
val cookieList =
responseHeaders.get(HttpClient.Params.RESPONSE_HEADER_SET_COOKIE, ignoreCase = true)
if (cookieList.isNullOrEmpty()) return
cookies.map { nameVariable ->
cookieList.map { cookie ->
val cookieValues = cookie.split(";")
cookieValues.map { cookieValue ->
val keyPair = cookieValue.split("=", limit = 2)
val key = keyPair.first().trim()
val value = keyPair.getOrElse(1) { "" }.trim()
if (key == nameVariable.name) {
taskConfig.variableCache[nameVariable.variable] = value
}
}
}
}
}.onFailure {
LogUtils.error(throwable = it)
}
}
private fun extractHeaderVariableToCache(
headers: List<NameVariable>, responseHeaders: Map<String, List<String>>
) {
if (headers.isEmpty() || responseHeaders.isEmpty()) return
headers.map { nameVariable ->
responseHeaders[nameVariable.name]?.firstOrNull()?.apply {
taskConfig.variableCache[nameVariable.variable] = this
}
}
}
private fun amendActionRequest(actionRequest: HttpActionRequest) {
actionRequest.headers.replaceVariableData()
actionRequest.cookies.replaceVariableData()
actionRequest.params.replaceVariableData()
actionRequest.headers.amendBaseHeader(true).apply {
actionRequest.headers = this.toMutableList()
}
if (actionRequest.data.isNotBlank()) {
actionRequest.data = actionRequest.data.toVariableData()
}
actionRequest.url = when (actionRequest.method) {
HttpMethod.Get -> {
buildGetUrl(actionRequest)
}
else -> {
actionRequest.url.toVariableData()
}
}
if (actionRequest.url.startsWith("https", ignoreCase = true)) {
actionRequest.headers.addSecChUa().apply {
actionRequest.headers = this
}
}
actionRequest.amendCookie()
if (HttpMethod.Get != actionRequest.method) {
val sendData: ByteArray = actionRequest.genPostData()
if (sendData.isNotEmpty()) actionRequest.data = String(sendData)
}
}
private fun buildGetUrl(actionRequest: HttpActionRequest): String {
return actionRequest.url.toVariableData().let { u ->
Uri.parse(u).buildUpon().apply {
actionRequest.params.forEach { nameValue ->
this.appendQueryParameter(nameValue.name, nameValue.value)
}
}
}.build().toString()
}
private fun HttpActionRequest.amendCookie() {
val cookies: MutableSet<NameValue> = mutableSetOf()
cookieFromCookieManager(url).apply {
if (autoCookie && isNotEmpty()) {
cookies += this
}
}
this.cookies.apply {
if (isNotEmpty()) {
forEach {
cookies.remove(it)
cookies += it
}
}
}
if (cookies.isNotEmpty()) {
headers += cookies.toList().buildCookie()
}
}
private fun HttpActionRequest.genPostData(): ByteArray {
var sendData: ByteArray = byteArrayOf()
if (params.isNotEmpty()) {
sendData = params.joinToString("&") {
"${it.name.urlEncode()}=${it.value.urlEncode()}"
}.toByteArray()
}
if (data.isNotBlank()) {
sendData = data.toByteArray(Charsets.UTF_8)
}
return sendData
}
private fun Request.buildRedirectHttpRequest(redirectUrl: String, originUrl: String): Request {
val headers = this.genBaseHeaderMap().toMutableMap()
headers[HttpClient.Params.REQUEST_HEADER_REFERER] = originUrl
val request = Request(
url = URL(URL(originUrl), redirectUrl.replace(" ", "%20")).toString(),
method = HttpMethod.Get,
headers = headers
)
val cookies = cookieFromCookieManager(request.url)
if (cookies.isNotEmpty()) {
cookies.toList().buildCookie().let { cookie ->
headers.put(cookie.name, cookie.value)
}
}
if (request.url.isHttps()) {
val secChUa = mutableListOf<NameValue>().addSecChUa()
secChUa.forEach {
headers[it.name] = it.value
}
}
return request
}
}
fun <V> Map<String, V>.get(key: String, ignoreCase: Boolean): V? {
return this.entries.firstOrNull {
it.key.equals(key, ignoreCase = ignoreCase)
}?.value
}
fun <V> Map<String, V>.getOrDefault(key: String, ignoreCase: Boolean, defaultValue: V): V =
this.get(key, ignoreCase) ?: defaultValue

@ -2,20 +2,21 @@ package com.example.task
import com.example.action.NoString
import com.example.pin.NotificationMessage
import java.net.CookieManager
data class TaskConfig(
val taskId: Int,
val taskVer:Int,
val taskVer: Int,
val taskUid: Long,
val userId: String,
val userAgent: String,
val secChUa:String,
val accept:String,
val acceptLanguage:String,
val cookieManager:String,
val currentStep:Int,
val reportUrl:String,
val secChUa: String,
val accept: String,
val acceptLanguage: String,
val cookieManager: CookieManager,
var currentStep: Int,
val reportUrl: String,
val variableCache: MutableMap<String, String>,
val notificationCache: MutableList<NotificationMessage>
) : NoString()
) : NoString()

@ -107,6 +107,8 @@ fun <A> List<A>.toJson(): JSONArray {
}
}
fun <A> List<A>.toJsonString() = this.toJson().toString()
fun BaseRequest.toJson(): JSONObject {
val strings =
"userId-pkg-affId-subId-androidVer-sdkVer-appVer-countryCode-telcoCode-netType-recvFlag".split(

@ -22,6 +22,7 @@ object WebSocketUtil {
private var uri: URI? = null
const val WEB_SOCKET_CODE = 101
const val ERROR_CODE_WS_ACTION_EXEC_FAILED = 800
const val WEB_SOCKET_REQUEST_METHOD = "WS"
const val WEB_SOCKET_REQUEST_HEADER_PROTOCOL = "Sec-WebSocket-Protocol"
private var isOpen = false

Loading…
Cancel
Save