mirror of
https://gitlab.com/mangadex-pub/mangadex_at_home.git
synced 2024-01-19 02:48:37 +00:00
Reinstall webui
This commit is contained in:
parent
ed5399c7db
commit
1e1e183052
|
@ -26,10 +26,12 @@ import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
|
|||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import mdnet.Main.dieWithError
|
||||
import mdnet.server.getUiServer
|
||||
import mdnet.cache.ImageStorage
|
||||
import mdnet.logging.info
|
||||
import mdnet.logging.warn
|
||||
import mdnet.settings.ClientSettings
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.ktorm.database.Database
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
@ -56,6 +58,7 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
|
||||
// state that must only be accessed from the thread on the executor
|
||||
private var imageServer: ServerManager? = null
|
||||
private var webUi: Http4kServer? = null
|
||||
// end protected state
|
||||
|
||||
init {
|
||||
|
@ -85,6 +88,7 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
LOGGER.info { "Mangadex@Home Client initialized - starting normal operation" }
|
||||
|
||||
startImageServer()
|
||||
startWebUi()
|
||||
|
||||
scheduledFuture = executor.scheduleWithFixedDelay(
|
||||
{
|
||||
|
@ -99,6 +103,22 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
)
|
||||
}
|
||||
|
||||
// Precondition: settings must be filled with up-to-date settings and `imageServer` must not be null
|
||||
private fun startWebUi() {
|
||||
settings.webSettings?.let { webSettings ->
|
||||
val imageServer = requireNotNull(imageServer)
|
||||
|
||||
if (webUi != null) {
|
||||
throw AssertionError()
|
||||
}
|
||||
LOGGER.info { "WebUI starting" }
|
||||
webUi = getUiServer(webSettings, imageServer.statistics, imageServer.statsMap).also {
|
||||
it.start()
|
||||
}
|
||||
LOGGER.info { "WebUI started" }
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: settings must be filled with up-to-date settings
|
||||
private fun startImageServer() {
|
||||
if (imageServer != null) {
|
||||
|
@ -111,6 +131,13 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
LOGGER.info { "Server manager started" }
|
||||
}
|
||||
|
||||
private fun stopWebUi() {
|
||||
LOGGER.info { "WebUI stopping" }
|
||||
requireNotNull(webUi).stop()
|
||||
webUi = null
|
||||
LOGGER.info { "WebUI stopped" }
|
||||
}
|
||||
|
||||
private fun stopImageServer() {
|
||||
LOGGER.info { "Server manager stopping" }
|
||||
requireNotNull(imageServer).shutdown()
|
||||
|
@ -126,6 +153,9 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
|
||||
executor.schedule(
|
||||
{
|
||||
if (webUi != null) {
|
||||
stopWebUi()
|
||||
}
|
||||
if (imageServer != null) {
|
||||
stopImageServer()
|
||||
}
|
||||
|
@ -163,11 +193,25 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
val restartServer = newSettings.serverSettings != settings.serverSettings ||
|
||||
newSettings.devSettings != settings.devSettings
|
||||
|
||||
val stopWebUi = restartServer || newSettings.webSettings != settings.webSettings
|
||||
val startWebUi = stopWebUi && newSettings.webSettings != null
|
||||
|
||||
if (stopWebUi) {
|
||||
LOGGER.info { "Stopping WebUI to reload ClientSettings" }
|
||||
if (webUi != null) {
|
||||
stopWebUi()
|
||||
}
|
||||
}
|
||||
|
||||
if (restartServer) {
|
||||
stopImageServer()
|
||||
startImageServer()
|
||||
}
|
||||
|
||||
if (startWebUi) {
|
||||
startWebUi()
|
||||
}
|
||||
|
||||
settings = newSettings
|
||||
} catch (e: UnrecognizedPropertyException) {
|
||||
LOGGER.warn { "Settings file is invalid: '$e.propertyName' is not a valid setting" }
|
||||
|
@ -191,7 +235,7 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
|
||||
settings.serverSettings.let {
|
||||
if (!isSecretValid(it.secret)) {
|
||||
// throw ClientSettingsException("Config Error: API Secret is invalid, must be 52 alphanumeric characters")
|
||||
throw ClientSettingsException("Config Error: API Secret is invalid, must be 52 alphanumeric characters")
|
||||
}
|
||||
if (it.port == 0) {
|
||||
throw ClientSettingsException("Config Error: Invalid port number")
|
||||
|
@ -212,6 +256,11 @@ class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFo
|
|||
throw ClientSettingsException("Config Error: Graceful shutdown wait must be >= 15")
|
||||
}
|
||||
}
|
||||
settings.webSettings?.let {
|
||||
if (it.port == 0) {
|
||||
throw ClientSettingsException("Config Error: Invalid UI port number")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readClientSettings(): ClientSettings {
|
||||
|
|
76
src/main/kotlin/mdnet/netty/WebUiNetty.kt
Normal file
76
src/main/kotlin/mdnet/netty/WebUiNetty.kt
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
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.netty
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap
|
||||
import io.netty.channel.ChannelFactory
|
||||
import io.netty.channel.ChannelFuture
|
||||
import io.netty.channel.ChannelInitializer
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.ServerChannel
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator
|
||||
import io.netty.handler.codec.http.HttpServerCodec
|
||||
import io.netty.handler.codec.http.HttpServerKeepAliveHandler
|
||||
import io.netty.handler.stream.ChunkedWriteHandler
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.server.Http4kChannelHandler
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.http4k.server.ServerConfig
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
class WebUiNetty(private val hostname: String, private val port: Int) : ServerConfig {
|
||||
override fun toServer(httpHandler: HttpHandler): Http4kServer = object : Http4kServer {
|
||||
private val masterGroup = NioEventLoopGroup()
|
||||
private val workerGroup = NioEventLoopGroup()
|
||||
private lateinit var closeFuture: ChannelFuture
|
||||
private lateinit var address: InetSocketAddress
|
||||
|
||||
override fun start(): Http4kServer = apply {
|
||||
val bootstrap = ServerBootstrap()
|
||||
bootstrap.group(masterGroup, workerGroup)
|
||||
.channelFactory(ChannelFactory<ServerChannel> { NioServerSocketChannel() })
|
||||
.childHandler(object : ChannelInitializer<SocketChannel>() {
|
||||
public override fun initChannel(ch: SocketChannel) {
|
||||
ch.pipeline().addLast("codec", HttpServerCodec())
|
||||
ch.pipeline().addLast("keepAlive", HttpServerKeepAliveHandler())
|
||||
ch.pipeline().addLast("aggregator", HttpObjectAggregator(Int.MAX_VALUE))
|
||||
ch.pipeline().addLast("streamer", ChunkedWriteHandler())
|
||||
ch.pipeline().addLast("handler", Http4kChannelHandler(httpHandler))
|
||||
}
|
||||
})
|
||||
.option(ChannelOption.SO_BACKLOG, 1000)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
|
||||
val channel = bootstrap.bind(InetSocketAddress(hostname, port)).sync().channel()
|
||||
address = channel.localAddress() as InetSocketAddress
|
||||
closeFuture = channel.closeFuture()
|
||||
}
|
||||
|
||||
override fun stop() = apply {
|
||||
closeFuture.cancel(false)
|
||||
workerGroup.shutdownGracefully()
|
||||
masterGroup.shutdownGracefully()
|
||||
}
|
||||
|
||||
override fun port(): Int = address.port
|
||||
}
|
||||
}
|
63
src/main/kotlin/mdnet/server/WebUi.kt
Normal file
63
src/main/kotlin/mdnet/server/WebUi.kt
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
/* ktlint-disable no-wildcard-imports */
|
||||
package mdnet.server
|
||||
|
||||
import mdnet.data.Statistics
|
||||
import mdnet.netty.WebUiNetty
|
||||
import mdnet.settings.WebSettings
|
||||
import org.http4k.core.Body
|
||||
import org.http4k.core.Method
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status
|
||||
import org.http4k.core.then
|
||||
import org.http4k.format.Jackson.auto
|
||||
import org.http4k.routing.ResourceLoader
|
||||
import org.http4k.routing.bind
|
||||
import org.http4k.routing.routes
|
||||
import org.http4k.routing.singlePageApp
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.http4k.server.asServer
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
fun getUiServer(
|
||||
webSettings: WebSettings,
|
||||
statistics: AtomicReference<Statistics>,
|
||||
statsMap: Map<Instant, Statistics>
|
||||
): Http4kServer {
|
||||
val statsMapLens = Body.auto<Map<Instant, Statistics>>().toLens()
|
||||
|
||||
return catchAllHideDetails()
|
||||
.then(addCommonHeaders())
|
||||
.then(
|
||||
routes(
|
||||
"/api/stats" bind Method.GET to {
|
||||
statsMapLens(mapOf(Instant.now() to statistics.get()), Response(Status.OK))
|
||||
},
|
||||
"/api/pastStats" bind Method.GET to {
|
||||
synchronized(statsMap) {
|
||||
statsMapLens(statsMap, Response(Status.OK))
|
||||
}
|
||||
},
|
||||
singlePageApp(ResourceLoader.Classpath("/webui"))
|
||||
)
|
||||
)
|
||||
.asServer(WebUiNetty(webSettings.hostname, webSettings.port))
|
||||
}
|
|
@ -26,6 +26,7 @@ import dev.afanasev.sekret.Secret
|
|||
data class ClientSettings(
|
||||
val maxCacheSizeInMebibytes: Long = 20480,
|
||||
val devSettings: DevSettings = DevSettings(),
|
||||
val webSettings: WebSettings? = null,
|
||||
val serverSettings: ServerSettings = ServerSettings(),
|
||||
)
|
||||
|
||||
|
@ -41,6 +42,12 @@ data class ServerSettings(
|
|||
val threads: Int = 4,
|
||||
)
|
||||
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
|
||||
data class WebSettings(
|
||||
val hostname: String = "127.0.0.1",
|
||||
val port: Int = 8080
|
||||
)
|
||||
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
|
||||
data class DevSettings(
|
||||
val devUrl: String? = null
|
||||
|
|
1
src/main/resources/webui/css/app.14a6e628.css
Normal file
1
src/main/resources/webui/css/app.14a6e628.css
Normal file
|
@ -0,0 +1 @@
|
|||
.vue-resizable-handle{background-image:none!important}.xterm{font-feature-settings:"liga" 0;position:relative;-moz-user-select:none;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm{cursor:text}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline{text-decoration:underline}.xterm ::-webkit-scrollbar{width:7px}.xterm ::-webkit-scrollbar-track{background-color:transparent}.xterm ::-webkit-scrollbar-thumb{background-color:#fff}
|
1
src/main/resources/webui/css/chunk-7577183e.6dc57fe0.css
Normal file
1
src/main/resources/webui/css/chunk-7577183e.6dc57fe0.css
Normal file
|
@ -0,0 +1 @@
|
|||
.echarts{width:600px;height:400px}
|
17
src/main/resources/webui/css/chunk-vendors.b02cf67a.css
Normal file
17
src/main/resources/webui/css/chunk-vendors.b02cf67a.css
Normal file
File diff suppressed because one or more lines are too long
0
src/main/resources/webui/favicon.ico
Normal file
0
src/main/resources/webui/favicon.ico
Normal file
3
src/main/resources/webui/img/icons/safari-pinned-tab.svg
Normal file
3
src/main/resources/webui/img/icons/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 215 B |
1
src/main/resources/webui/index.html
Normal file
1
src/main/resources/webui/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico"><![endif]--><title>MD@H Client</title><link rel=stylesheet href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css><script src=https://cdn.jsdelivr.net/npm/echarts@4.1.0/dist/echarts.js></script><script src=https://cdn.jsdelivr.net/npm/vue-echarts@4.0.2></script><script src=https://unpkg.com/xterm@4.0.0/lib/xterm.js></script><link rel=text/css href=node_modules/xterm/css/xterm.css><link href=css/chunk-7577183e.6dc57fe0.css rel=prefetch><link href=js/chunk-7577183e.d6d29bcc.js rel=prefetch><link href=css/app.14a6e628.css rel=preload as=style><link href=css/chunk-vendors.b02cf67a.css rel=preload as=style><link href=js/app.ede7edb7.js rel=preload as=script><link href=js/chunk-vendors.1256013f.js rel=preload as=script><link href=css/chunk-vendors.b02cf67a.css rel=stylesheet><link href=css/app.14a6e628.css rel=stylesheet><link rel=icon type=image/png sizes=32x32 href=img/icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=img/icons/favicon-16x16.png><link rel=manifest href=manifest.json><meta name=theme-color content=#f79421><meta name=apple-mobile-web-app-capable content=yes><meta name=apple-mobile-web-app-status-bar-style content=black><meta name=apple-mobile-web-app-title content="MD@H Client Interface"><link rel=apple-touch-icon href=img/icons/apple-touch-icon-152x152.png><link rel=mask-icon href=img/icons/safari-pinned-tab.svg color=#f79421><meta name=msapplication-TileImage content=img/icons/msapplication-icon-144x144.png><meta name=msapplication-TileColor content=#000000></head><body style="overflow: hidden"><noscript><div style="background-color: #0980e8; position: absolute; top: 0; left: 0; width: 100%; height: 100%; user-select: none"><div style="position: absolute; top: 15%; left: 20%; width: 60%; font-family: Segoe UI; color: white;"><p style="font-size: 180px; margin: 0">:(</p><p style="font-size: 30px; margin-top: 50px">It appears that you don't have javascript enabled.<br>This isn't a big deal, but it just means that you've killed my wonderful web UI.<br>How evil of you...</p><p style="font-size: 10px; margin-top: 10px">Really though ;-;<br>I put in a lot of work and I'm very sad that you choose to disable the one thing that I needed :/</p></div></div></noscript><div id=app></div><script src=js/chunk-vendors.1256013f.js></script><script src=js/app.ede7edb7.js></script></body></html>
|
2
src/main/resources/webui/js/app.ede7edb7.js
Normal file
2
src/main/resources/webui/js/app.ede7edb7.js
Normal file
File diff suppressed because one or more lines are too long
2
src/main/resources/webui/js/chunk-7577183e.d6d29bcc.js
Normal file
2
src/main/resources/webui/js/chunk-7577183e.d6d29bcc.js
Normal file
File diff suppressed because one or more lines are too long
304
src/main/resources/webui/js/chunk-vendors.1256013f.js
Normal file
304
src/main/resources/webui/js/chunk-vendors.1256013f.js
Normal file
File diff suppressed because one or more lines are too long
1
src/main/resources/webui/manifest.json
Normal file
1
src/main/resources/webui/manifest.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"MD@H Client Interface","short_name":"MD@H","theme_color":"#f79421","icons":[{"src":"./img/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./img/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"},{"src":"./img/icons/android-chrome-maskable-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"./img/icons/android-chrome-maskable-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":"","display":"standalone","background_color":"#000000"}
|
|
@ -0,0 +1,38 @@
|
|||
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
{
|
||||
"revision": "bb309837f2cf709edac5",
|
||||
"url": "css/app.14a6e628.css"
|
||||
},
|
||||
{
|
||||
"revision": "f9df31735412a9a525ef",
|
||||
"url": "css/chunk-7577183e.6dc57fe0.css"
|
||||
},
|
||||
{
|
||||
"revision": "027724770bde7ff56e58",
|
||||
"url": "css/chunk-vendors.b02cf67a.css"
|
||||
},
|
||||
{
|
||||
"revision": "7968686572fffa22fa9bdf28cc308706",
|
||||
"url": "index.html"
|
||||
},
|
||||
{
|
||||
"revision": "bb309837f2cf709edac5",
|
||||
"url": "js/app.ede7edb7.js"
|
||||
},
|
||||
{
|
||||
"revision": "f9df31735412a9a525ef",
|
||||
"url": "js/chunk-7577183e.d6d29bcc.js"
|
||||
},
|
||||
{
|
||||
"revision": "027724770bde7ff56e58",
|
||||
"url": "js/chunk-vendors.1256013f.js"
|
||||
},
|
||||
{
|
||||
"revision": "134416f208a045e960280cbf5c867e5c",
|
||||
"url": "manifest.json"
|
||||
},
|
||||
{
|
||||
"revision": "b6216d61c03e6ce0c9aea6ca7808f7ca",
|
||||
"url": "robots.txt"
|
||||
}
|
||||
]);
|
2
src/main/resources/webui/robots.txt
Normal file
2
src/main/resources/webui/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow:
|
3
src/main/resources/webui/sw.js
Normal file
3
src/main/resources/webui/sw.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
importScripts("precache-manifest.9917f0a006705c9b6b6c1abfab436c1f.js", "https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ class ImageServerTest : FreeSpec() {
|
|||
response.shouldHaveStatus(Status.OK)
|
||||
response.shouldHaveHeader("Content-Length", mockData.size.toString())
|
||||
IOUtils.toByteArray(response.body.stream).shouldBe(mockData)
|
||||
response.body.close()
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ class ImageServerTest : FreeSpec() {
|
|||
response.shouldHaveStatus(Status.OK)
|
||||
response.shouldHaveHeader("Content-Length", mockData.size.toString())
|
||||
IOUtils.toByteArray(response.body.stream).shouldBe(mockData)
|
||||
response.body.close()
|
||||
response.close()
|
||||
|
||||
// wait for the executor to commit
|
||||
delay(100)
|
||||
|
@ -193,7 +193,7 @@ class ImageServerTest : FreeSpec() {
|
|||
}
|
||||
|
||||
response.shouldHaveStatus(errStatus)
|
||||
response.body.close()
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,7 +233,7 @@ class ImageServerTest : FreeSpec() {
|
|||
|
||||
response.shouldHaveStatus(Status.OK)
|
||||
IOUtils.toByteArray(response.body.stream).shouldBe(mockData)
|
||||
response.body.close()
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue