mirror of
https://gitlab.com/mangadex-pub/mangadex_at_home.git
synced 2024-01-19 02:48:37 +00:00
166 lines
5.7 KiB
Kotlin
166 lines
5.7 KiB
Kotlin
/*
|
|
Mangadex@Home
|
|
Copyright (c) 2020, MangaDex Network
|
|
This file is part of MangaDex@Home.
|
|
|
|
MangaDex@Home is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
MangaDex@Home is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this MangaDex@Home. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package mdnet.server
|
|
|
|
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig
|
|
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
|
|
import io.github.resilience4j.micrometer.tagged.TaggedCircuitBreakerMetrics
|
|
import io.micrometer.core.instrument.FunctionCounter
|
|
import io.micrometer.prometheus.PrometheusMeterRegistry
|
|
import mdnet.cache.ImageStorage
|
|
import mdnet.data.Statistics
|
|
import mdnet.logging.info
|
|
import mdnet.logging.warn
|
|
import mdnet.metrics.GeoIpMetricsFilterBuilder
|
|
import mdnet.metrics.PostTransactionLabeler
|
|
import mdnet.netty.Netty
|
|
import mdnet.settings.MetricsSettings
|
|
import mdnet.settings.RemoteSettings
|
|
import mdnet.settings.ServerSettings
|
|
import org.http4k.core.*
|
|
import org.http4k.filter.*
|
|
import org.http4k.routing.*
|
|
import org.http4k.server.Http4kServer
|
|
import org.http4k.server.asServer
|
|
import org.slf4j.LoggerFactory
|
|
import java.time.Duration
|
|
|
|
fun getServer(
|
|
storage: ImageStorage,
|
|
remoteSettings: RemoteSettings,
|
|
serverSettings: ServerSettings,
|
|
metricsSettings: MetricsSettings,
|
|
statistics: Statistics,
|
|
registry: PrometheusMeterRegistry,
|
|
client: HttpHandler
|
|
): Http4kServer {
|
|
val circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults()
|
|
TaggedCircuitBreakerMetrics
|
|
.ofCircuitBreakerRegistry(circuitBreakerRegistry)
|
|
.bindTo(registry)
|
|
|
|
val circuitBreaker = circuitBreakerRegistry.circuitBreaker(
|
|
"upstream",
|
|
CircuitBreakerConfig.custom()
|
|
.slidingWindow(50, 20, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
|
|
.permittedNumberOfCallsInHalfOpenState(10)
|
|
.slowCallDurationThreshold(Duration.ofSeconds(20))
|
|
.waitDurationInOpenState(Duration.ofSeconds(20))
|
|
.build()
|
|
)
|
|
|
|
circuitBreaker.eventPublisher.onFailureRateExceeded {
|
|
LOGGER.warn { "Circuit breaker has exceeded failure rate" }
|
|
}
|
|
|
|
circuitBreaker.eventPublisher.onSlowCallRateExceeded {
|
|
LOGGER.warn { "Circuit breaker has exceeded slow call rate" }
|
|
}
|
|
|
|
circuitBreaker.eventPublisher.onReset {
|
|
LOGGER.warn { "Circuit breaker has rest" }
|
|
}
|
|
|
|
circuitBreaker.eventPublisher.onStateTransition {
|
|
LOGGER.warn { "Circuit breaker has moved from ${it.stateTransition.fromState} to ${it.stateTransition.toState}" }
|
|
}
|
|
|
|
val circuited = ResilienceFilters.CircuitBreak(
|
|
circuitBreaker,
|
|
isError = { r: Response -> !r.status.successful }
|
|
)
|
|
|
|
val upstream = ClientFilters.MicrometerMetrics.RequestTimer(registry)
|
|
.then(ClientFilters.SetBaseUriFrom(remoteSettings.imageServer))
|
|
.then(circuited)
|
|
.then(client)
|
|
|
|
val imageServer = ImageServer(
|
|
storage = storage,
|
|
upstream = upstream,
|
|
registry = registry
|
|
)
|
|
|
|
FunctionCounter.builder(
|
|
"client_sent_bytes",
|
|
statistics,
|
|
{ it.bytesSent.get().toDouble() }
|
|
).register(registry)
|
|
|
|
val verifier = TokenVerifier(
|
|
tokenKey = remoteSettings.tokenKey,
|
|
)
|
|
|
|
return timeRequest()
|
|
.then(addCommonHeaders())
|
|
.then(catchAllHideDetails())
|
|
.then(
|
|
routes(
|
|
"/{token}/data/{chapterHash}/{fileName}" bind Method.GET to verifier.then(
|
|
imageServer.handler(
|
|
dataSaver = false,
|
|
)
|
|
),
|
|
"/{token}/data-saver/{chapterHash}/{fileName}" bind Method.GET to verifier.then(
|
|
imageServer.handler(
|
|
dataSaver = true,
|
|
)
|
|
),
|
|
"/data/{chapterHash}/{fileName}" bind Method.GET to verifier.then(
|
|
imageServer.handler(
|
|
dataSaver = false,
|
|
)
|
|
),
|
|
"/data-saver/{chapterHash}/{fileName}" bind Method.GET to verifier.then(
|
|
imageServer.handler(
|
|
dataSaver = true,
|
|
)
|
|
),
|
|
"/prometheus" bind Method.GET to {
|
|
Response(Status.OK).body(registry.scrape())
|
|
},
|
|
).withFilter(
|
|
ServerFilters.MicrometerMetrics.RequestTimer(registry, labeler = PostTransactionLabeler())
|
|
).withFilter(
|
|
GeoIpMetricsFilterBuilder(metricsSettings.enableGeoip, metricsSettings.geoipLicenseKey, registry).build()
|
|
)
|
|
)
|
|
.asServer(Netty(remoteSettings.tls!!, serverSettings, statistics))
|
|
}
|
|
|
|
private val LOGGER = LoggerFactory.getLogger(ImageServer::class.java)
|
|
|
|
fun timeRequest(): Filter {
|
|
return Filter { next: HttpHandler ->
|
|
{ request: Request ->
|
|
val cleanedUri = request.uri.path.replaceBefore("/data", "/{token}")
|
|
|
|
LOGGER.info { "Request for $cleanedUri received" }
|
|
|
|
val start = System.currentTimeMillis()
|
|
val response = next(request)
|
|
val latency = System.currentTimeMillis() - start
|
|
|
|
LOGGER.info { "Request for $cleanedUri completed (TTFB) in ${latency}ms" }
|
|
|
|
response
|
|
}
|
|
}
|
|
}
|