diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpHeaderNames.java b/components/api/src/main/java/com/hotels/styx/api/HttpHeaderNames.java index 41ef56eff1..4ac500ec1e 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpHeaderNames.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpHeaderNames.java @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2018 Expedia Inc. + Copyright (C) 2013-2019 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ public final class HttpHeaderNames { public static final CharSequence TRAILER = newEntity(Names.TRAILER); public static final CharSequence UPGRADE = newEntity(Names.UPGRADE); public static final CharSequence VIA = newEntity(Names.VIA); + public static final CharSequence CACHE_CONTROL = newEntity(Names.CACHE_CONTROL); private HttpHeaderNames() { } diff --git a/components/proxy/src/main/java/com/hotels/styx/admin/AdminServerBuilder.java b/components/proxy/src/main/java/com/hotels/styx/admin/AdminServerBuilder.java index 900dad6542..72a7db5621 100644 --- a/components/proxy/src/main/java/com/hotels/styx/admin/AdminServerBuilder.java +++ b/components/proxy/src/main/java/com/hotels/styx/admin/AdminServerBuilder.java @@ -38,6 +38,7 @@ import com.hotels.styx.admin.handlers.StartupConfigHandler; import com.hotels.styx.admin.handlers.StyxConfigurationHandler; import com.hotels.styx.admin.handlers.ThreadsHandler; +import com.hotels.styx.admin.handlers.UptimeHandler; import com.hotels.styx.admin.handlers.VersionTextHandler; import com.hotels.styx.admin.tasks.OriginsCommandHandler; import com.hotels.styx.admin.tasks.OriginsReloadCommandHandler; @@ -131,6 +132,7 @@ private HttpHandler adminEndpoints(StyxConfig styxConfig, StartupConfig startupC httpRouter.aggregate("/", new IndexHandler(indexLinkPaths(styxConfig))); httpRouter.aggregate("/version.txt", new VersionTextHandler(styxConfig.versionFiles(startupConfig))); httpRouter.aggregate("/admin", new IndexHandler(indexLinkPaths(styxConfig))); + httpRouter.aggregate("/admin/uptime", new UptimeHandler(environment.metricRegistry())); httpRouter.aggregate("/admin/ping", new PingHandler()); httpRouter.aggregate("/admin/threads", new ThreadsHandler()); httpRouter.aggregate("/admin/current_requests", new CurrentRequestsHandler(CurrentRequestTracker.INSTANCE)); diff --git a/components/proxy/src/main/java/com/hotels/styx/admin/handlers/UptimeHandler.java b/components/proxy/src/main/java/com/hotels/styx/admin/handlers/UptimeHandler.java new file mode 100644 index 0000000000..9ccf4842c5 --- /dev/null +++ b/components/proxy/src/main/java/com/hotels/styx/admin/handlers/UptimeHandler.java @@ -0,0 +1,50 @@ +/* + Copyright (C) 2013-2019 Expedia Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.hotels.styx.admin.handlers; + +import com.hotels.styx.api.Eventual; +import com.hotels.styx.api.HttpInterceptor; +import com.hotels.styx.api.HttpRequest; +import com.hotels.styx.api.HttpResponse; +import com.hotels.styx.api.MetricRegistry; +import com.hotels.styx.api.WebServiceHandler; + +import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; +import static com.hotels.styx.api.HttpResponseStatus.OK; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Provides an uptime via admin interface. + */ +public class UptimeHandler implements WebServiceHandler { + private final MetricRegistry metricRegistry; + + public UptimeHandler(MetricRegistry metricRegistry) { + this.metricRegistry = metricRegistry; + } + + @Override + public Eventual handle(HttpRequest request, HttpInterceptor.Context context) { + Object uptime = metricRegistry.getGauges().get("jvm.uptime.formatted").getValue(); + + return Eventual.of(HttpResponse.response(OK) + .disableCaching() + .addHeader(CONTENT_TYPE, "application/json") + .body(format("\"%s\"", uptime), UTF_8) + .build()); + } +} diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/AdminInterfaceSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/AdminInterfaceSpec.kt new file mode 100644 index 0000000000..abb1e5e5a7 --- /dev/null +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/AdminInterfaceSpec.kt @@ -0,0 +1,46 @@ +package com.hotels.styx.admin + +import com.hotels.styx.support.StyxServerProvider +import com.hotels.styx.support.adminRequest +import io.kotlintest.Spec +import io.kotlintest.matchers.string.shouldMatch +import io.kotlintest.specs.FeatureSpec +import java.nio.charset.StandardCharsets.UTF_8 + +class AdminInterfaceSpec : FeatureSpec() { + val styxServer = StyxServerProvider( + defaultConfig = """ + --- + proxy: + connectors: + http: + port: 0 + + admin: + connectors: + http: + port: 0 + + httpPipeline: + type: StaticResponseHandler + config: + status: 200 + """.trimIndent() + ) + + init { + feature("Styx Server Admin Interface") { + styxServer.restart() + + scenario("Uptime endpoint") { + styxServer.adminRequest("/admin/uptime") + .bodyAs(UTF_8) + .shouldMatch("\"[0-9]{1,2}d [0-9]{1,2}h [0-9]{1,2}m\"".toRegex()) + } + } + } + + override fun afterSpec(spec: Spec) { + styxServer.stop() + } +} diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/PluginAdminInterfaceSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/PluginAdminInterfaceSpec.kt index 03afa65e76..fbc701366e 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/PluginAdminInterfaceSpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/PluginAdminInterfaceSpec.kt @@ -17,18 +17,13 @@ package com.hotels.styx.admin import com.google.common.net.MediaType.PLAIN_TEXT_UTF_8 import com.hotels.styx.api.HttpHandler -import com.hotels.styx.api.HttpHeaderNames.HOST import com.hotels.styx.api.HttpInterceptor -import com.hotels.styx.api.HttpRequest.get -import com.hotels.styx.api.HttpResponse import com.hotels.styx.api.LiveHttpRequest import com.hotels.styx.api.plugins.spi.Plugin -import com.hotels.styx.client.StyxHttpClient import com.hotels.styx.common.http.handler.HttpAggregator import com.hotels.styx.common.http.handler.StaticBodyHttpHandler import com.hotels.styx.support.StyxServerProvider -import com.hotels.styx.support.adminHostHeader -import com.hotels.styx.support.wait +import com.hotels.styx.support.adminRequest import io.kotlintest.Spec import io.kotlintest.matchers.string.shouldInclude import io.kotlintest.shouldBe @@ -117,14 +112,6 @@ class PluginAdminInterfaceSpec : FeatureSpec() { } } - val client: StyxHttpClient = StyxHttpClient.Builder().build() - - fun StyxServerProvider.adminRequest(endpoint: String): HttpResponse = client - .send(get(endpoint) - .header(HOST, this().adminHostHeader()) - .build()) - .wait() - override fun afterSpec(spec: Spec) { styxServer.stop() } diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt index f947fae0f3..93166b5f7b 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.hotels.styx.StyxConfig import com.hotels.styx.StyxServer +import com.hotels.styx.api.HttpHeaderNames import com.hotels.styx.api.HttpHeaderNames.HOST import com.hotels.styx.api.HttpRequest import com.hotels.styx.api.HttpResponse @@ -106,6 +107,14 @@ class StyxServerProvider( } } +val testClient: StyxHttpClient = StyxHttpClient.Builder().build() + +fun StyxServerProvider.adminRequest(endpoint: String, debug: Boolean = false): HttpResponse = testClient + .send(HttpRequest.get(endpoint) + .header(HttpHeaderNames.HOST, this().adminHostHeader()) + .build()) + .wait(debug = debug) + fun CompletableFuture.wait(debug: Boolean = false) = this.toMono() .doOnNext { if (debug) {