1
0
Fork 1
mirror of https://gitlab.com/mangadex-pub/mangadex_at_home.git synced 2024-01-19 02:48:37 +00:00
mangadex_at_home/src/main/kotlin/mdnet/server/ImageServer.kt
carbotaniuman f9cf5729b4 Next
2021-03-11 20:09:14 +00:00

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
}
}
}