mirror of https://github.com/hykilpikonna/AquaDX
update
parent
db5343fba3
commit
340003c568
|
@ -5,6 +5,9 @@ import io.ktor.client.*
|
|||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.micrometer.core.instrument.*
|
||||
import io.micrometer.core.instrument.Timer
|
||||
import io.micrometer.core.instrument.Metrics as Micrometer
|
||||
import jakarta.persistence.Query
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -22,6 +25,7 @@ import java.time.LocalDate
|
|||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
@ -214,3 +218,45 @@ val <S> Pair<*, S>.r get() = component2()
|
|||
|
||||
// Database
|
||||
val Query.exec get() = resultList.map { (it as Array<*>).toList() }
|
||||
|
||||
// Metrics
|
||||
object Metrics {
|
||||
@PublishedApi
|
||||
internal inline fun <reified T : Any> expandLabels(labels: T): Array<String> {
|
||||
return T::class.memberProperties.flatMap { prop ->
|
||||
listOf(prop.name, prop.get(labels)?.toString() ?: throw IllegalArgumentException("Missing value for label ${prop.name}"))
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal data class MetricCacheKey(val metricName: String, val labels: Any)
|
||||
|
||||
@PublishedApi
|
||||
internal val counterCache = ConcurrentHashMap<MetricCacheKey, Counter>()
|
||||
|
||||
inline fun <reified T : Any> counter(metricName: String): (T) -> Counter {
|
||||
return { labels ->
|
||||
counterCache.computeIfAbsent(MetricCacheKey(metricName, labels)) {
|
||||
Counter
|
||||
.builder(metricName)
|
||||
.tags(*expandLabels(labels))
|
||||
.register(Micrometer.globalRegistry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal val timerCache = ConcurrentHashMap<MetricCacheKey, Timer>()
|
||||
|
||||
inline fun <reified T : Any> timer(metricName: String): (T) -> Timer {
|
||||
return { labels ->
|
||||
timerCache.computeIfAbsent(MetricCacheKey(metricName, labels)) {
|
||||
Timer
|
||||
.builder(metricName)
|
||||
.publishPercentiles(0.5, 0.75, 0.90, 0.95, 0.99)
|
||||
.tags(*expandLabels(labels))
|
||||
.register(Micrometer.globalRegistry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import icu.samnyan.aqua.net.components.JWT
|
|||
import icu.samnyan.aqua.sega.allnet.AllNetProps
|
||||
import icu.samnyan.aqua.sega.allnet.KeyChipRepo
|
||||
import icu.samnyan.aqua.sega.allnet.KeychipSession
|
||||
import icu.samnyan.aqua.sega.allnet.TokenChecker
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import jakarta.persistence.*
|
||||
|
|
|
@ -4,6 +4,7 @@ import ext.toHex
|
|||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetProps
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.ByteBufUtil
|
||||
import io.netty.buffer.Unpooled
|
||||
|
@ -25,6 +26,7 @@ import kotlin.jvm.optionals.getOrNull
|
|||
class AimeDB(
|
||||
val cardService: CardService,
|
||||
val us: AquaUserServices,
|
||||
val allNetProps: AllNetProps,
|
||||
): ChannelInboundHandlerAdapter() {
|
||||
val logger: Logger = LoggerFactory.getLogger(AimeDB::class.java)
|
||||
|
||||
|
@ -65,7 +67,14 @@ class AimeDB(
|
|||
logger.info("AimeDB /${handler.name} : (game ${base.gameId}, keychip ${base.keychipId})")
|
||||
|
||||
// Check keychip
|
||||
if (!us.validKeychip(base.keychipId)) return logger.warn("> Rejected: Keychip not found")
|
||||
if (!us.validKeychip(base.keychipId)) {
|
||||
if (allNetProps.keychipPermissiveForTesting) {
|
||||
logger.warn("> Accepted invalid keychip ${base.keychipId} in permissive mode")
|
||||
} else {
|
||||
logger.warn("> Rejected: Keychip not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
handler.fn(data)?.let { ctx.write(it) }
|
||||
} finally {
|
||||
|
|
|
@ -25,6 +25,7 @@ class AllNetProps {
|
|||
var port: Int? = null
|
||||
val keychipSesExpire: Long = 172800000 // milliseconds
|
||||
var checkKeychip: Boolean = false
|
||||
var keychipPermissiveForTesting: Boolean = false
|
||||
var redirect: String = "web"
|
||||
|
||||
var placeName: String = ""
|
||||
|
@ -102,9 +103,11 @@ class AllNet(
|
|||
// game_id SDEZ, ver 1.35, serial A0000001234, ip, firm_ver 50000, boot_ver 0000,
|
||||
// encode UTF-8, format_ver 3, hops 1, token 2010451813
|
||||
val reqMap = decodeAllNet(dataStream.readAllBytes())
|
||||
var serial = reqMap["serial"] ?: ""
|
||||
val serial = reqMap["serial"] ?: ""
|
||||
logger.info("AllNet /PowerOn : $reqMap")
|
||||
|
||||
var session: String? = null
|
||||
|
||||
// Proper keychip authentication
|
||||
if (props.checkKeychip) {
|
||||
// If it's a user keychip, it should be in user database
|
||||
|
@ -112,11 +115,20 @@ class AllNet(
|
|||
if (u != null) {
|
||||
// Create a new session for the user
|
||||
logger.info("> Keychip authenticated: ${u.auId} ${u.computedName}")
|
||||
serial = keychipSessionService.new(u).token
|
||||
session = keychipSessionService.new(u, reqMap["game_id"] ?: "").token
|
||||
}
|
||||
|
||||
// Check if it's a whitelisted keychip
|
||||
else if (serial.isEmpty() || !keychipRepo.existsByKeychipId(serial)) {
|
||||
else if (!serial.isEmpty() && keychipRepo.existsByKeychipId(serial)) {
|
||||
session = keychipSessionService.new(null, reqMap["game_id"] ?: "").token
|
||||
}
|
||||
|
||||
else if (props.keychipPermissiveForTesting) {
|
||||
logger.warn("> Accepted invalid keychip $serial in permissive mode")
|
||||
session = keychipSessionService.new(null, reqMap["game_id"] ?: "").token
|
||||
}
|
||||
|
||||
else {
|
||||
// This will cause an allnet auth bad on client side
|
||||
return "".also { logger.warn("> Rejected: Keychip not found") }
|
||||
}
|
||||
|
@ -127,7 +139,7 @@ class AllNet(
|
|||
|
||||
val formatVer = reqMap["format_ver"] ?: ""
|
||||
val resp = props.map.toMutableMap() + mapOf(
|
||||
"uri" to switchUri(localAddr, localPort, gameId, ver, serial),
|
||||
"uri" to switchUri(localAddr, localPort, gameId, ver, session),
|
||||
"host" to props.host.ifBlank { localAddr },
|
||||
)
|
||||
|
||||
|
@ -160,15 +172,15 @@ class AllNet(
|
|||
return resp.toUrl() + "\n"
|
||||
}
|
||||
|
||||
private fun switchUri(localAddr: Str, localPort: Str, gameId: Str, ver: Str, serial: Str): Str {
|
||||
private fun switchUri(localAddr: Str, localPort: Str, gameId: Str, ver: Str, session: Str?): Str {
|
||||
val addr = props.host.ifBlank { localAddr }
|
||||
val port = props.port?.toString() ?: localPort
|
||||
|
||||
// If keychip authentication is enabled, the game URLs will be set to /gs/{token}/{game}/...
|
||||
val base = if (props.checkKeychip) "gs/$serial" else "g"
|
||||
val base = if (session != null) "gs/$session" else "g"
|
||||
|
||||
return "http://$addr:$port/$base/" + when (gameId) {
|
||||
"SDBT" -> "chu2/$ver/$serial/"
|
||||
"SDBT" -> "chu2/$ver/$session/"
|
||||
"SDHD" -> "chu3/$ver/"
|
||||
"SDGS" -> "chu3/$ver/" // International (c3exp)
|
||||
"SBZV" -> "diva/"
|
||||
|
|
|
@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletRequest
|
|||
import jakarta.servlet.http.HttpServletRequestWrapper
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
@ -30,6 +31,13 @@ class TokenChecker(
|
|||
) : HandlerInterceptor {
|
||||
val log = LoggerFactory.getLogger(TokenChecker::class.java)
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(TokenChecker::class.java)
|
||||
|
||||
private val currentSession = ThreadLocal<KeychipSession?>()
|
||||
fun getCurrentSession() = currentSession.get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request before it's processed.
|
||||
*/
|
||||
|
@ -48,6 +56,8 @@ class TokenChecker(
|
|||
if (token.isNotBlank() && (keyChipRepo.existsByKeychipId(token) || session != null
|
||||
|| (frontierProps.enabled && frontierProps.ftk == token)))
|
||||
{
|
||||
currentSession.set(session)
|
||||
|
||||
// Forward the request
|
||||
val w = RewriteWrapper(req, token).apply { setAttribute("token", token) }
|
||||
req.getRequestDispatcher(w.requestURI).forward(w, resp)
|
||||
|
|
|
@ -21,7 +21,10 @@ import java.security.SecureRandom
|
|||
class KeychipSession(
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "au_id")
|
||||
var user: AquaNetUser = AquaNetUser(),
|
||||
var user: AquaNetUser? = null,
|
||||
|
||||
@Column(length = 4)
|
||||
val gameId: String,
|
||||
|
||||
@Id
|
||||
@Column(length = 32)
|
||||
|
@ -69,8 +72,8 @@ class KeychipSessionService(
|
|||
/**
|
||||
* Create a new session.
|
||||
*/
|
||||
fun new(user: AquaNetUser): KeychipSession {
|
||||
val session = KeychipSession(user = user)
|
||||
fun new(user: AquaNetUser?, gameId: String): KeychipSession {
|
||||
val session = KeychipSession(user = user, gameId = gameId)
|
||||
return keychipSessionRepo.save(session)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,12 @@ import ext.*
|
|||
import icu.samnyan.aqua.sega.chunithm.handler.impl.GetGameIdlistHandler
|
||||
import icu.samnyan.aqua.sega.chusan.handler.*
|
||||
import icu.samnyan.aqua.sega.general.BaseHandler
|
||||
import io.micrometer.core.instrument.Timer
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.time.TimeSource
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
|
||||
/**
|
||||
|
@ -70,6 +73,12 @@ class ChusanServletController(
|
|||
val getUserNetBattleRankingInfo: GetUserNetBattleRankingInfoHandler,
|
||||
val getGameMapAreaCondition: GetGameMapAreaConditionHandler
|
||||
) {
|
||||
data class ApiLabel(val api: String)
|
||||
data class ApiErrorLabel(val api: String, val error: String)
|
||||
val apiCountMetric = Metrics.counter<ApiLabel>("aquadx_chusan_api_count")
|
||||
val apiErrorCountMetric = Metrics.counter<ApiErrorLabel>("aquadx_chusan_api_error_count")
|
||||
val apiLatencyMetric = Metrics.timer<ApiLabel>("aquadx_chusan_api_latency")
|
||||
|
||||
val logger = LoggerFactory.getLogger(ChusanServletController::class.java)
|
||||
|
||||
val getUserCtoCPlay = BaseHandler { """{"userId":"${it["userId"]}","orderBy":"0","count":"0","userCtoCPlayList":[]}""" }
|
||||
|
@ -110,6 +119,9 @@ class ChusanServletController(
|
|||
@API("/{endpoint}")
|
||||
fun handle(@PV endpoint: Str, @RB request: MutableMap<Str, Any>, @PV version: Str): Any {
|
||||
var api = endpoint
|
||||
val startTime = TimeSource.Monotonic.markNow()
|
||||
var timer: Timer? = null
|
||||
try {
|
||||
request["version"] = version
|
||||
|
||||
// Export version
|
||||
|
@ -118,15 +130,23 @@ class ChusanServletController(
|
|||
request["c3exp"] = true
|
||||
}
|
||||
|
||||
apiCountMetric(ApiLabel(api)).increment()
|
||||
logger.info("Chu3 $api : $request")
|
||||
|
||||
if (api in noopEndpoint) {
|
||||
return """{"returnCode":"1"}"""
|
||||
}
|
||||
|
||||
timer = apiLatencyMetric(ApiLabel(api))
|
||||
return handlers[api]?.handle(request) ?: {
|
||||
logger.warn("Chu3 $api not found")
|
||||
"""{"returnCode":"1","apiName":"$api"}"""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
apiErrorCountMetric(ApiErrorLabel(api, e.javaClass.name)).increment()
|
||||
throw e;
|
||||
} finally {
|
||||
timer?.record(startTime.elapsedNow().toJavaDuration())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package icu.samnyan.aqua.sega.general
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import icu.samnyan.aqua.sega.allnet.KeychipSession
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
|
|
|
@ -2,15 +2,21 @@ package icu.samnyan.aqua.sega.maimai2
|
|||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.utils.ApiException
|
||||
import icu.samnyan.aqua.sega.allnet.KeychipSession
|
||||
import icu.samnyan.aqua.sega.general.BaseHandler
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.*
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import io.ktor.client.request.*
|
||||
import io.micrometer.core.instrument.Timer
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.time.TimeSource
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
|
@ -40,6 +46,12 @@ class Maimai2ServletController(
|
|||
private val GAME_SETTING_TIME_FMT = DateTimeFormatter.ofPattern("HH:mm:00")
|
||||
}
|
||||
|
||||
data class ApiLabel(val api: String)
|
||||
data class ApiErrorLabel(val api: String, val error: String)
|
||||
val apiCountMetric = Metrics.counter<ApiLabel>("aquadx_maimai2_api_count")
|
||||
val apiErrorCountMetric = Metrics.counter<ApiErrorLabel>("aquadx_maimai2_api_error_count")
|
||||
val apiLatencyMetric = Metrics.timer<ApiLabel>("aquadx_maimai2_api_latency")
|
||||
|
||||
val getUserExtend = UserReqHandler { _, userId -> mapOf(
|
||||
"userId" to userId,
|
||||
"userExtend" to (repos.userExtend.findSingleByUser_Card_ExtId(userId)() ?: (404 - "User not found"))
|
||||
|
@ -339,7 +351,10 @@ class Maimai2ServletController(
|
|||
|
||||
@API("/{api}")
|
||||
fun handle(@PathVariable api: String, @RequestBody request: Map<String, Any>): Any {
|
||||
val startTime = TimeSource.Monotonic.markNow()
|
||||
var timer: Timer? = null
|
||||
try {
|
||||
apiCountMetric(ApiLabel(api)).increment()
|
||||
logger.info("Mai2 < $api : ${request.toJson()}") // TODO: Optimize logging
|
||||
|
||||
if (api in noopEndpoint) {
|
||||
|
@ -352,6 +367,7 @@ class Maimai2ServletController(
|
|||
return staticEndpoint[api]!!
|
||||
}
|
||||
|
||||
timer = apiLatencyMetric(ApiLabel(api))
|
||||
return handlers[api]?.handle(request)?.let { if (it is String) it else it.toJson() }?.also {
|
||||
if (api !in setOf("GetUserItemApi", "GetGameEventApi"))
|
||||
logger.info("Mai2 > $api : $it")
|
||||
|
@ -359,9 +375,17 @@ class Maimai2ServletController(
|
|||
logger.warn("Mai2 > $api not found")
|
||||
"""{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
|
||||
}
|
||||
} catch (e: ApiException) {
|
||||
} catch (e: Exception) {
|
||||
if (e is ApiException) {
|
||||
apiErrorCountMetric(ApiErrorLabel(api, e.code.toString())).increment()
|
||||
// It's a bad practice to return 200 ok on error, but this is what maimai does so we have to follow
|
||||
return ResponseEntity.ok().body("""{"returnCode":0,"apiName":"com.sega.maimai2servlet.api.$api","message":"${e.message?.replace("\"", "\\\"")} - ${e.code}"}""")
|
||||
} else {
|
||||
apiErrorCountMetric(ApiErrorLabel(api, e.javaClass.name)).increment()
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
timer?.record(startTime.elapsedNow().toJavaDuration())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package icu.samnyan.aqua.sega.maimai2.handler
|
||||
|
||||
import ext.Metrics
|
||||
import ext.millis
|
||||
import icu.samnyan.aqua.sega.allnet.TokenChecker
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
|
||||
import icu.samnyan.aqua.sega.general.BaseHandler
|
||||
import icu.samnyan.aqua.sega.maimai2.Maimai2ServletController.ApiLabel
|
||||
import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPlaylog
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog
|
||||
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
|
||||
|
@ -24,11 +27,23 @@ class UploadUserPlaylogHandler(
|
|||
companion object {
|
||||
@JvmStatic
|
||||
val playBacklog = mutableMapOf<Long, MutableList<BacklogEntry>>()
|
||||
|
||||
val VALID_GAME_IDS = setOf("SDEZ", "SDGA", "SDGB")
|
||||
}
|
||||
|
||||
data class GameIdVersionLabel(val gameId: String, val version: String)
|
||||
val gameVersionCountMetric = Metrics.counter<GameIdVersionLabel>("aquadx_maimai2_game_version_count")
|
||||
|
||||
override fun handle(request: Map<String, Any>): String {
|
||||
val req = mapper.convert(request, UploadUserPlaylog::class.java)
|
||||
|
||||
val version = tryParseGameVersion(req.userPlaylog.version)
|
||||
if (version != null) {
|
||||
val session = TokenChecker.getCurrentSession()
|
||||
val gameId = if (session?.gameId in VALID_GAME_IDS) session!!.gameId else ""
|
||||
gameVersionCountMetric(GameIdVersionLabel(gameId, version)).increment()
|
||||
}
|
||||
|
||||
// Save if the user is registered
|
||||
val u = userDataRepository.findByCardExtId(req.userId).getOrNull()
|
||||
if (u != null) playlogRepo.save(req.userPlaylog.apply { user = u })
|
||||
|
@ -52,4 +67,13 @@ class UploadUserPlaylogHandler(
|
|||
playBacklog.filter { (_, v) -> v.isEmpty() || v[0].time - now > 300_000 }.toList()
|
||||
.forEach { (k, _) -> playBacklog.remove(k) }
|
||||
}
|
||||
|
||||
private fun tryParseGameVersion(version: Int): String? {
|
||||
val major = version / 1000000
|
||||
val minor = version / 1000 % 1000
|
||||
if (major != 1) return null
|
||||
if (minor !in 0..99) return null
|
||||
// e.g. "1.30", minor should have two digits
|
||||
return "$major.${minor.toString().padStart(2, '0')}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import icu.samnyan.aqua.sega.wacca.WaccaOptionType.*
|
|||
import icu.samnyan.aqua.sega.wacca.model.BaseRequest
|
||||
import icu.samnyan.aqua.sega.wacca.model.db.*
|
||||
import io.ktor.client.utils.*
|
||||
import io.micrometer.core.instrument.Timer
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.ResponseEntity
|
||||
|
@ -20,6 +21,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.time.TimeSource
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
val empty = emptyList<Any>()
|
||||
|
||||
|
@ -32,6 +35,12 @@ class WaccaServer {
|
|||
@Autowired lateinit var rp: WaccaRepos
|
||||
@Autowired lateinit var wacca: Wacca
|
||||
|
||||
data class ApiLabel(val api: String)
|
||||
data class ApiErrorLabel(val api: String, val error: String)
|
||||
val apiCountMetric = Metrics.counter<ApiLabel>("aquadx_wacca_api_count")
|
||||
val apiErrorCountMetric = Metrics.counter<ApiErrorLabel>("aquadx_wacca_api_error_count")
|
||||
val apiLatencyMetric = Metrics.timer<ApiLabel>("aquadx_wacca_api_latency")
|
||||
|
||||
val handlerMap = mutableMapOf<String, (BaseRequest, List<Any>) -> Any>()
|
||||
val cacheMap = mutableMapOf<String, String>()
|
||||
|
||||
|
@ -79,14 +88,21 @@ class WaccaServer {
|
|||
/** Handle all requests */
|
||||
@API("/api/**")
|
||||
fun handle(req: HttpServletRequest, @RB body: String): Any {
|
||||
val startTime = TimeSource.Monotonic.markNow()
|
||||
var timer: Timer? = null
|
||||
var api = ""
|
||||
return try {
|
||||
val path = req.requestURI.removePrefix("/g/wacca").removePrefix("/WaccaServlet")
|
||||
.removePrefix("/api").removePrefix("/").lowercase()
|
||||
|
||||
if (path !in cacheMap && path !in handlerMap) return resp("[]", 1, "Not Found")
|
||||
api = path
|
||||
apiCountMetric(ApiLabel(api)).increment()
|
||||
if (path in cacheMap) return resp(cacheMap[path]!!)
|
||||
if (path !in handlerMap) return resp("[]", 1, "Not Found")
|
||||
|
||||
log.info("Wacca < $path : $body")
|
||||
|
||||
timer = apiLatencyMetric(ApiLabel(api))
|
||||
val br = JACKSON.parse<BaseRequest>(body)
|
||||
handlerMap[path]!!(br, br.params).let { when (it) {
|
||||
is String -> resp(it)
|
||||
|
@ -94,10 +110,16 @@ class WaccaServer {
|
|||
else -> error("Invalid response type ${it.javaClass}")
|
||||
} }.also { log.info("Wacca > $path : ${it.body}") }
|
||||
}
|
||||
catch (e: ApiException) { resp("[]", e.code, e.message ?: "") }
|
||||
catch (e: ApiException) {
|
||||
apiErrorCountMetric(ApiErrorLabel(api, e.code.toString())).increment()
|
||||
resp("[]", e.code, e.message ?: "")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
apiErrorCountMetric(ApiErrorLabel(api, e.javaClass.name)).increment()
|
||||
log.error("Wacca > Error", e)
|
||||
resp("[]", 500, e.message ?: "")
|
||||
} finally {
|
||||
timer?.record(startTime.elapsedNow().toJavaDuration())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,3 +38,6 @@ spring.output.ansi.enabled=always
|
|||
management.server.port=8081
|
||||
management.endpoint.prometheus.enabled=true
|
||||
management.endpoints.web.exposure.include=prometheus
|
||||
management.metrics.distribution.percentiles.http.server.requests=0.5, 0.75, 0.9, 0.95, 0.99
|
||||
management.metrics.distribution.percentiles.tasks.scheduled.execution=0.5, 0.75, 0.9, 0.95, 0.99
|
||||
management.metrics.distribution.percentiles.spring.data.repository.invocations=0.5, 0.75, 0.9, 0.95, 0.99
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE allnet_keychip_sessions
|
||||
ADD game_id VARCHAR(4) NULL;
|
Loading…
Reference in New Issue