Skip to content

Commit

Permalink
#635 added support for GET healthcheck / liveness endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesdbloom committed Jan 30, 2020
1 parent 1c251f6 commit 9f27af0
Show file tree
Hide file tree
Showing 18 changed files with 383 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class ConfigurationProperties {
private static final String DEFAULT_CORS_ALLOW_METHODS = "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE";
private static final String DEFAULT_CORS_ALLOW_CREDENTIALS = "true";
private static final int DEFAULT_CORS_MAX_AGE_IN_SECONDS = 300;
private static final String DEFAULT_LIVENESS_HTTP_GET_PATH = "";

private static final String MOCKSERVER_PROPERTY_FILE = "mockserver.propertyFile";
private static final String MOCKSERVER_ENABLE_CORS_FOR_API = "mockserver.enableCORSForAPI";
Expand Down Expand Up @@ -117,6 +118,7 @@ public class ConfigurationProperties {
private static final String MOCKSERVER_CORS_ALLOW_METHODS = "mockserver.corsAllowMethods";
private static final String MOCKSERVER_CORS_ALLOW_CREDENTIALS = "mockserver.corsAllowCredentials";
private static final String MOCKSERVER_CORS_MAX_AGE_IN_SECONDS = "mockserver.corsMaxAgeInSeconds";
private static final String MOCKSERVER_LIVENESS_HTTP_GET_PATH = "mockserver.livenessHttpGetPath";

private static final Properties PROPERTIES = readPropertyFile();
private static final Set<String> ALL_SUBJECT_ALTERNATIVE_DOMAINS = Sets.newConcurrentHashSet();
Expand Down Expand Up @@ -198,6 +200,7 @@ private static ForwardProxyTLSX509CertificatesTrustManager validateTrustManagerT
private static int maxChunkSize = readIntegerProperty(MOCKSERVER_MAX_CHUNK_SIZE, "MOCKSERVER_MAX_CHUNK_SIZE", DEFAULT_MAX_CHUNK_SIZE);
private static boolean preventCertificateDynamicUpdate = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_PREVENT_CERTIFICATE_DYNAMIC_UPDATE, "MOCKSERVER_PREVENT_CERTIFICATE_DYNAMIC_UPDATE", DEFAULT_PREVENT_CERTIFICATE_DYNAMIC_UPDATE));
private static boolean alwaysCloseConnections = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_ALWAYS_CLOSE_SOCKET_CONNECTIONS, "MOCKSERVER_ALWAYS_CLOSE_SOCKET_CONNECTIONS", DEFAULT_MOCKSERVER_ALWAYS_CLOSE_SOCKET_CONNECTIONS));
private static String livenessHttpGetPath = readPropertyHierarchically(MOCKSERVER_LIVENESS_HTTP_GET_PATH, "MOCKSERVER_LIVENESS_HTTP_GET_PATH", DEFAULT_LIVENESS_HTTP_GET_PATH);

@VisibleForTesting
static void reset() {
Expand All @@ -209,14 +212,18 @@ static void reset() {
javaLoggerLogLevel = DEFAULT_LOG_LEVEL;
metricsEnabled = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_METRICS_ENABLED, "MOCKSERVER_METRICS_ENABLED", "" + false));
disableSystemOut = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_DISABLE_SYSTEM_OUT, "MOCKSERVER_DISABLE_SYSTEM_OUT", "" + false));
enableMTLS = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_TLS_MUTUAL_AUTHENTICATION_REQUIRED, "MOCKSERVER_TLS_MUTUAL_AUTHENTICATION_REQUIRED", DEFAULT_TLS_MUTUAL_AUTHENTICATION_REQUIRED));
forwardProxyTLSX509CertificatesTrustManager = validateTrustManagerType(readPropertyHierarchically(MOCKSERVER_FORWARD_PROXY_TLS_X509_CERTIFICATES_TRUST_MANAGER, "MOCKSERVER_FORWARD_PROXY_TLS_X509_CERTIFICATES_TRUST_MANAGER", DEFAULT_FORWARD_PROXY_TLS_X509_CERTIFICATES_TRUST_MANAGER));
forwardProxyTLSCustomTrustX509Certificates = readPropertyHierarchically(MOCKSERVER_FORWARD_PROXY_TLS_CUSTOM_TRUST_X509_CERTIFICATES, "MOCKSERVER_FORWARD_PROXY_TLS_CUSTOM_TRUST_X509_CERTIFICATES", DEFAULT_FORWARD_PROXY_TLS_CUSTOM_TRUST_X509_CERTIFICATES);
enableCORSForAPI = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_ENABLE_CORS_FOR_API, "MOCKSERVER_ENABLE_CORS_FOR_API", DEFAULT_ENABLE_CORS_FOR_API));
enableCORSForAPIHasBeenSetExplicitly = System.getProperty(MOCKSERVER_ENABLE_CORS_FOR_API) != null ||
PROPERTIES.getProperty(MOCKSERVER_ENABLE_CORS_FOR_API) != null;
enableCORSForAPIHasBeenSetExplicitly = System.getProperty(MOCKSERVER_ENABLE_CORS_FOR_API) != null || PROPERTIES.getProperty(MOCKSERVER_ENABLE_CORS_FOR_API) != null;
enableCORSForAllResponses = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_ENABLE_CORS_FOR_ALL_RESPONSES, "MOCKSERVER_ENABLE_CORS_FOR_ALL_RESPONSES", DEFAULT_ENABLE_CORS_FOR_ALL_RESPONSES));
maxInitialLineLength = readIntegerProperty(MOCKSERVER_MAX_INITIAL_LINE_LENGTH, "MOCKSERVER_MAX_INITIAL_LINE_LENGTH", DEFAULT_MAX_INITIAL_LINE_LENGTH);
maxHeaderSize = readIntegerProperty(MOCKSERVER_MAX_HEADER_SIZE, "MOCKSERVER_MAX_HEADER_SIZE", DEFAULT_MAX_HEADER_SIZE);
maxChunkSize = readIntegerProperty(MOCKSERVER_MAX_CHUNK_SIZE, "MOCKSERVER_MAX_CHUNK_SIZE", DEFAULT_MAX_CHUNK_SIZE);
preventCertificateDynamicUpdate = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_PREVENT_CERTIFICATE_DYNAMIC_UPDATE, "MOCKSERVER_PREVENT_CERTIFICATE_DYNAMIC_UPDATE", DEFAULT_PREVENT_CERTIFICATE_DYNAMIC_UPDATE));
alwaysCloseConnections = Boolean.parseBoolean(readPropertyHierarchically(MOCKSERVER_ALWAYS_CLOSE_SOCKET_CONNECTIONS, "MOCKSERVER_ALWAYS_CLOSE_SOCKET_CONNECTIONS", DEFAULT_MOCKSERVER_ALWAYS_CLOSE_SOCKET_CONNECTIONS));
livenessHttpGetPath = readPropertyHierarchically(MOCKSERVER_LIVENESS_HTTP_GET_PATH, "MOCKSERVER_LIVENESS_HTTP_GET_PATH", DEFAULT_LIVENESS_HTTP_GET_PATH);
}

private static String propertyFile() {
Expand Down Expand Up @@ -830,6 +837,15 @@ public static void corsMaxAgeInSeconds(int ageInSeconds) {
System.setProperty(MOCKSERVER_CORS_MAX_AGE_IN_SECONDS, "" + ageInSeconds);
}

public static String livenessHttpGetPath() {
return livenessHttpGetPath;
}

public static void livenessHttpGetPath(String livenessPath) {
System.setProperty(MOCKSERVER_LIVENESS_HTTP_GET_PATH, livenessPath);
livenessHttpGetPath = readPropertyHierarchically(MOCKSERVER_LIVENESS_HTTP_GET_PATH, "MOCKSERVER_LIVENESS_HTTP_GET_PATH", DEFAULT_LIVENESS_HTTP_GET_PATH);
}

private static void validateHostAndPort(String hostAndPort, String propertyName, String mockserverSocksProxy) {
String errorMessage = "Invalid " + propertyName + " property must include <host>:<port> for example \"127.0.0.1:1090\" or \"localhost:1090\"";
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ public void verify(VerificationSequence verification, Consumer<String> resultCon
}

public boolean handle(HttpRequest request, ResponseWriter responseWriter, boolean warDeployment) {
CompletableFuture<Boolean> canHandle = new CompletableFuture<>();

mockServerLogger.logEvent(
new LogEntry()
Expand All @@ -441,83 +440,94 @@ public boolean handle(HttpRequest request, ResponseWriter responseWriter, boolea
.setArguments(request)
);

if (request.matches("PUT", PATH_PREFIX + "/expectation", "/expectation")) {
if (request.matches("PUT")) {

for (Expectation expectation : expectationSerializer.deserializeArray(request.getBodyAsString(), false)) {
if (!warDeployment || validateSupportedFeatures(expectation, request, responseWriter)) {
add(expectation);
CompletableFuture<Boolean> canHandle = new CompletableFuture<>();

if (request.matches("PUT", PATH_PREFIX + "/expectation", "/expectation")) {

for (Expectation expectation : expectationSerializer.deserializeArray(request.getBodyAsString(), false)) {
if (!warDeployment || validateSupportedFeatures(expectation, request, responseWriter)) {
add(expectation);
}
}
}
responseWriter.writeResponse(request, CREATED);
canHandle.complete(true);
responseWriter.writeResponse(request, CREATED);
canHandle.complete(true);

} else if (request.matches("PUT", PATH_PREFIX + "/clear", "/clear")) {
} else if (request.matches("PUT", PATH_PREFIX + "/clear", "/clear")) {

clear(request);
responseWriter.writeResponse(request, OK);
canHandle.complete(true);
clear(request);
responseWriter.writeResponse(request, OK);
canHandle.complete(true);

} else if (request.matches("PUT", PATH_PREFIX + "/reset", "/reset")) {
} else if (request.matches("PUT", PATH_PREFIX + "/reset", "/reset")) {

reset();
responseWriter.writeResponse(request, OK);
canHandle.complete(true);
reset();
responseWriter.writeResponse(request, OK);
canHandle.complete(true);

} else if (request.matches("PUT", PATH_PREFIX + "/retrieve", "/retrieve")) {
} else if (request.matches("PUT", PATH_PREFIX + "/retrieve", "/retrieve")) {

responseWriter.writeResponse(request, retrieve(request), true);
canHandle.complete(true);
responseWriter.writeResponse(request, retrieve(request), true);
canHandle.complete(true);

} else if (request.matches("PUT", PATH_PREFIX + "/verify", "/verify")) {
} else if (request.matches("PUT", PATH_PREFIX + "/verify", "/verify")) {

Verification verification = verificationSerializer.deserialize(request.getBodyAsString());
mockServerLogger.logEvent(
new LogEntry()
.setType(VERIFICATION)
.setLogLevel(Level.INFO)
.setHttpRequest(verification.getHttpRequest())
.setMessageFormat("verifying requests that match:{}")
.setArguments(verification)
);
verify(verification, result -> {
if (isEmpty(result)) {
responseWriter.writeResponse(request, ACCEPTED);

} else {
responseWriter.writeResponse(request, NOT_ACCEPTABLE, result, MediaType.create("text", "plain").toString());
}
canHandle.complete(true);
});

} else if (request.matches("PUT", PATH_PREFIX + "/verifySequence", "/verifySequence")) {

VerificationSequence verificationSequence = verificationSequenceSerializer.deserialize(request.getBodyAsString());
mockServerLogger.logEvent(
new LogEntry()
.setType(VERIFICATION)
.setLogLevel(Level.INFO)
.setHttpRequests(verificationSequence.getHttpRequests().toArray(new HttpRequest[0]))
.setMessageFormat("verifying sequence that match:{}")
.setArguments(verificationSequence)
);
verify(verificationSequence, result -> {
if (isEmpty(result)) {
responseWriter.writeResponse(request, ACCEPTED);
} else {
responseWriter.writeResponse(request, NOT_ACCEPTABLE, result, MediaType.create("text", "plain").toString());
}
canHandle.complete(true);
});

Verification verification = verificationSerializer.deserialize(request.getBodyAsString());
mockServerLogger.logEvent(
new LogEntry()
.setType(VERIFICATION)
.setLogLevel(Level.INFO)
.setHttpRequest(verification.getHttpRequest())
.setMessageFormat("verifying requests that match:{}")
.setArguments(verification)
);
verify(verification, result -> {
if (isEmpty(result)) {
responseWriter.writeResponse(request, ACCEPTED);
} else {
canHandle.complete(false);
}

} else {
responseWriter.writeResponse(request, NOT_ACCEPTABLE, result, MediaType.create("text", "plain").toString());
}
canHandle.complete(true);
});

} else if (request.matches("PUT", PATH_PREFIX + "/verifySequence", "/verifySequence")) {

VerificationSequence verificationSequence = verificationSequenceSerializer.deserialize(request.getBodyAsString());
mockServerLogger.logEvent(
new LogEntry()
.setType(VERIFICATION)
.setLogLevel(Level.INFO)
.setHttpRequests(verificationSequence.getHttpRequests().toArray(new HttpRequest[0]))
.setMessageFormat("verifying sequence that match:{}")
.setArguments(verificationSequence)
);
verify(verificationSequence, result -> {
if (isEmpty(result)) {
responseWriter.writeResponse(request, ACCEPTED);
} else {
responseWriter.writeResponse(request, NOT_ACCEPTABLE, result, MediaType.create("text", "plain").toString());
}
canHandle.complete(true);
});
try {
return canHandle.get(maxFutureTimeout(), MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ignore) {
return false;
}

} else {
canHandle.complete(false);
}

try {
return canHandle.get(maxFutureTimeout(), MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ignore) {
return false;

}

}

@SuppressWarnings("rawtypes")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ public HttpRequest withSocketAddress(SocketAddress socketAddress) {
* Specify remote address if the remote address can't be derived from the host header,
* if no value is specified the host header will be used to determine remote address
*
* @param host the remote host or ip to send request to
* @param port the remote port to send request to
* @param host the remote host or ip to send request to
* @param port the remote port to send request to
* @param scheme the scheme to use for remote socket
*/
public HttpRequest withSocketAddress(String host, Integer port, SocketAddress.Scheme scheme) {
Expand Down Expand Up @@ -160,7 +160,11 @@ public NottableString getPath() {
return path;
}

public boolean matches(String method, String... paths) {
public boolean matches(final String method) {
return this.method.getValue().equals(method);
}

public boolean matches(final String method, final String... paths) {
boolean matches = false;
for (String path : paths) {
matches = this.method.getValue().equals(method) && this.path.getValue().equals(path);
Expand Down Expand Up @@ -560,7 +564,7 @@ public boolean containsHeader(String name) {
/**
* Returns true if a header with the specified name and value has been added
*
* @param name the header name
* @param name the header name
* @param value the header value
* @return true if a header has been added with that name otherwise false
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.mockserver.model;

import org.mockserver.Version;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -9,7 +11,14 @@
*/
public class PortBinding extends ObjectWithJsonToString {

private List<Integer> ports = new ArrayList<Integer>();
private static final String VERSION = Version.getVersion();
private static final String ARTIFACT_ID = Version.getArtifactId();
private static final String GROUP_ID = Version.getGroupId();

private List<Integer> ports = new ArrayList<>();
private final String version = VERSION;
private final String artifactId = ARTIFACT_ID;
private final String groupId = GROUP_ID;

public static PortBinding portBinding(Integer... ports) {
return portBinding(Arrays.asList(ports));
Expand All @@ -27,4 +36,16 @@ public PortBinding setPorts(List<Integer> ports) {
this.ports = ports;
return this;
}

public String getVersion() {
return version;
}

public String getArtifactId() {
return artifactId;
}

public String getGroupId() {
return groupId;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.mockserver.servlet.responsewriter;

import io.netty.handler.codec.http.HttpResponseStatus;
import org.mockserver.configuration.ConfigurationProperties;
import org.mockserver.cors.CORSHeaders;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.mappers.MockServerResponseToHttpServletResponseEncoder;
Expand All @@ -11,6 +12,7 @@
import javax.servlet.http.HttpServletResponse;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.mockserver.configuration.ConfigurationProperties.enableCORSForAPI;
import static org.mockserver.configuration.ConfigurationProperties.enableCORSForAllResponses;
import static org.mockserver.mock.HttpStateHandler.PATH_PREFIX;
Expand Down Expand Up @@ -61,7 +63,7 @@ public void writeResponse(final HttpRequest request, HttpResponse response, fina
if (apiResponse) {
response.withHeader("version", org.mockserver.Version.getVersion());
final String path = request.getPath().getValue();
if (!path.startsWith(PATH_PREFIX)) {
if (!path.startsWith(PATH_PREFIX) && !path.equals(ConfigurationProperties.livenessHttpGetPath())) {
response.withHeader("deprecated",
"\"" + path + "\" is deprecated use \"" + PATH_PREFIX + path + "\" instead");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,4 +1034,18 @@ public void shouldSetAndReadCORSMaxAgeInSeconds() {
assertEquals("100", System.getProperty("mockserver.corsMaxAgeInSeconds"));
assertEquals(100, corsMaxAgeInSeconds());
}

@Test
public void shouldSetAndReadLivenessHttpGetPath() {
// given
System.clearProperty("mockserver.livenessHttpGetPath");

// when
assertEquals("", livenessHttpGetPath());
livenessHttpGetPath("/livenessHttpGetPath");

// then
assertEquals("/livenessHttpGetPath", livenessHttpGetPath());
assertEquals("/livenessHttpGetPath", System.getProperty("mockserver.livenessHttpGetPath"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.io.IOException;
import java.util.Arrays;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.mockserver.character.Character.NEW_LINE;
import static org.mockserver.model.PortBinding.portBinding;
Expand Down Expand Up @@ -74,8 +76,10 @@ public void shouldSerializeCompleteObject() {
);

// then
assertEquals("{" + NEW_LINE +
" \"ports\" : [ 0, 1080, 0 ]" + NEW_LINE +
"}", jsonPortBinding);
assertThat(jsonPortBinding, containsString("{" + NEW_LINE +
" \"ports\" : [ 0, 1080, 0 ]," + NEW_LINE));
assertThat(jsonPortBinding, containsString("\"version\" : "));
assertThat(jsonPortBinding, containsString("\"artifactId\" : "));
assertThat(jsonPortBinding, containsString("\"groupId\" : "));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.io.IOException;
import java.util.Arrays;

import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
Expand Down Expand Up @@ -83,7 +84,7 @@ public void serialize() throws IOException {
public void serializeObjectHandlesException() throws IOException {
// given
thrown.expect(RuntimeException.class);
thrown.expectMessage("Exception while serializing portBinding to JSON with value { }");
thrown.expectMessage(containsString("Exception while serializing portBinding to JSON with value"));
// and
when(objectMapper.writerWithDefaultPrettyPrinter()).thenReturn(objectWriter);
when(objectWriter.writeValueAsString(any(PortBinding.class))).thenThrow(new RuntimeException("TEST EXCEPTION"));
Expand Down
Loading

0 comments on commit 9f27af0

Please sign in to comment.