diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java
index 38de96242..8176f1880 100644
--- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java
@@ -1,6 +1,8 @@
package net.trajano.ms.vertx;
import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Deque;
import java.util.LinkedList;
@@ -48,6 +50,10 @@ public class VertxMicroserviceEngine implements
@Autowired
private HttpServerOptions httpServerOptions;
+ private String theHostname;
+
+ private int thePort = -1;
+
private Vertx vertx;
@Autowired
@@ -73,6 +79,18 @@ public Object[] bootstrap() {
};
}
+ @Override
+ public String hostname() {
+
+ return theHostname;
+ }
+
+ @Override
+ public int port() {
+
+ return thePort;
+ }
+
@PostConstruct
public void start() {
@@ -113,8 +131,14 @@ public void start() {
SpringApplication.exit(applicationContext, () -> -1);
} else {
LOG.info("Listening on port {}", http.actualPort());
+ thePort = http.actualPort();
}
});
+ try {
+ theHostname = InetAddress.getLocalHost().getHostName();
+ } catch (final UnknownHostException e) {
+ throw new ExceptionInInitializerError(e);
+ }
}
@PreDestroy
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java
index ba1ec1a7f..798ad87ba 100644
--- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java
@@ -69,6 +69,8 @@ public JwtConsumer buildConsumer(final HttpsJwks jwks,
if (jwks != null) {
builder
.setVerificationKeyResolver(new HttpsJwksVerificationKeyResolver(jwks));
+ } else {
+ builder.setSkipSignatureVerification();
}
if (audience != null) {
builder
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java
index 8117e258b..8567d0068 100644
--- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java
@@ -27,6 +27,44 @@ public class DefaultAssertionRequiredPredicate implements
private static final Logger LOG = LoggerFactory.getLogger(DefaultAssertionRequiredPredicate.class);
+ /**
+ *
+ * The key logic for the test is as follows:
+ *
+ *
+ * Let:
+ *
+ *
+ *
+ * a = |
+ * resourceMethodHasRolesAllowed |
+ *
+ *
+ * b = |
+ * resourceClassHasRolesAllowed |
+ *
+ *
+ * c = |
+ * resourceMethodHasPermitAll |
+ *
+ *
+ * d = |
+ * resourceClassHasPermitAll |
+ *
+ *
+ *
+ * The rules that need to be applied translate to:
+ *
+ *
+ *
+ * = a || ( b && !c ) || ( !a && !b && !c && !d )
+ * = (a || b || !d) && ( a || !c )
+ *
+ *
+ * @param resourceInfo
+ * resource info
+ * @return true
if the resource is protected
+ */
@Override
public boolean test(final ResourceInfo resourceInfo) {
@@ -49,13 +87,6 @@ public boolean test(final ResourceInfo resourceInfo) {
} else if (resourceClassHasRolesAllowed && resourceClassHasPermitAll) {
throw new IllegalArgumentException("The resource class " + resourceClass + " may not have both @RolesAllowed and @PermitAll annotations.");
} else {
- // resourceMethodHasRolesAllowed OR
- // resourceClassHasRolesAllowed && !resourceMethodHasPermitAll OR
- // !resourceMethodHasRolesAllowed && !resourceClassHasRolesAllowed && !resourceMethodHasPermitAll && !resourceClassHasPermitAll
-
- // a || ( b && !c ) || ( !a && !b && !c && !d)
-
- // (a || b || !d) && ( a || !c)
return (resourceMethodHasRolesAllowed || resourceClassHasRolesAllowed || !resourceClassHasPermitAll) && (resourceMethodHasRolesAllowed || !resourceMethodHasPermitAll);
}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java
index a4d720817..604246f48 100644
--- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java
@@ -54,9 +54,6 @@ public class JsonExceptionMapper implements
@Context
private HttpHeaders headers;
- @Value("${microservice.show_request_uri:#{null}}")
- private Boolean showRequestUri;
-
@Value("${microservice.show_stack_trace:#{null}}")
private Boolean showStackTrace;
@@ -105,12 +102,10 @@ private void log(final Throwable exception) {
*/
public void setContextData(final HttpHeaders headers,
final UriInfo uriInfo,
- final boolean showRequestUri,
final boolean showStackTrace) {
this.headers = headers;
this.uriInfo = uriInfo;
- this.showRequestUri = showRequestUri;
this.showStackTrace = showStackTrace;
}
@@ -122,11 +117,9 @@ public void setContextData(final HttpHeaders headers,
@PostConstruct
public void setDebugFlags() {
- if (showRequestUri == null) {
- showRequestUri = LOG.isDebugEnabled();
- }
if (showStackTrace == null) {
showStackTrace = LOG.isDebugEnabled();
+ LOG.debug("stack trace enabled if this is shown");
}
}
@@ -152,7 +145,7 @@ public Response toResponse(final Throwable exception) {
.build();
} else {
return Response.status(status)
- .entity(new ErrorResponse(exception, headers, uriInfo, showStackTrace, showRequestUri))
+ .entity(new ErrorResponse(exception, uriInfo, showStackTrace))
.type(mediaType)
.build();
}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java
index 9f18d71ca..f5141cf24 100644
--- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java
@@ -2,7 +2,6 @@
import static net.trajano.ms.core.ErrorCodes.FORBIDDEN;
import static net.trajano.ms.core.ErrorCodes.UNAUTHORIZED_CLIENT;
-import static net.trajano.ms.core.Qualifiers.REQUEST_ID;
import java.net.URI;
import java.util.Arrays;
@@ -22,17 +21,20 @@
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.Provider;
-import net.trajano.ms.vertx.beans.CachedDataProvider;
import org.jose4j.jwk.HttpsJwks;
import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import net.trajano.ms.core.ErrorResponse;
+import net.trajano.ms.spi.MDCKeys;
+import net.trajano.ms.vertx.beans.CachedDataProvider;
import net.trajano.ms.vertx.beans.DefaultAssertionRequiredPredicate;
import net.trajano.ms.vertx.beans.JwtAssertionRequiredPredicate;
import net.trajano.ms.vertx.beans.JwtClaimsProcessor;
@@ -45,7 +47,7 @@
*/
@Component
@Provider
-@Priority(Priorities.AUTHORIZATION)
+@Priority(Priorities.AUTHORIZATION + 1)
public class JwtAssertionInterceptor implements
ContainerRequestFilter {
@@ -59,6 +61,8 @@ public class JwtAssertionInterceptor implements
private JwtAssertionRequiredPredicate assertionRequiredPredicate;
+ private CachedDataProvider cachedDataProvider;
+
private JwtClaimsProcessor claimsProcessor;
@Autowired(required = false)
@@ -70,8 +74,6 @@ public class JwtAssertionInterceptor implements
*/
private final ConcurrentMap jwks = new ConcurrentHashMap<>();
- private CachedDataProvider cachedDataProvider;
-
@Context
private ResourceInfo resourceInfo;
@@ -86,7 +88,7 @@ public void filter(final ContainerRequestContext requestContext) {
LOG.warn("Missing assertion on request for {}", requestContext.getUriInfo());
requestContext.abortWith(Response.status(Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, X_JWT_ASSERTION)
- .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "Missing assertion", requestContext.getHeaderString(REQUEST_ID)))
+ .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "Missing assertion"))
.build());
return;
}
@@ -106,7 +108,9 @@ public void filter(final ContainerRequestContext requestContext) {
}
final List audience = Arrays.asList(requestContext.getHeaderString(X_JWT_AUDIENCE).split(", "));
claims = cachedDataProvider.buildConsumer(httpsJwks, audience).processToClaims(assertion);
- } catch (final InvalidJwtException e) {
+ MDC.put(MDCKeys.JWT_ID, claims.getJwtId());
+ } catch (final InvalidJwtException
+ | MalformedClaimException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("JWT invalid", e);
} else {
@@ -114,7 +118,7 @@ public void filter(final ContainerRequestContext requestContext) {
}
requestContext.abortWith(Response.status(Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, X_JWT_ASSERTION)
- .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "JWT was not valid", requestContext.getHeaderString(REQUEST_ID)))
+ .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "JWT was not valid"))
.build());
return;
}
@@ -126,7 +130,7 @@ public void filter(final ContainerRequestContext requestContext) {
if (!validateClaims) {
LOG.warn("Validation of claims failed on request for {}", requestContext.getUriInfo());
requestContext.abortWith(Response.status(Status.FORBIDDEN)
- .entity(new ErrorResponse(FORBIDDEN, "Claims validation failed", requestContext.getHeaderString(REQUEST_ID)))
+ .entity(new ErrorResponse(FORBIDDEN, "Claims validation failed"))
.build());
}
}
@@ -154,16 +158,21 @@ public void setAssertionRequiredFunction(final JwtAssertionRequiredPredicate pre
assertionRequiredPredicate = predicate;
}
+ @Autowired
+ public void setCachedDataProvider(final CachedDataProvider cachedDataProvider) {
+
+ this.cachedDataProvider = cachedDataProvider;
+ }
+
@Autowired(required = false)
public void setClaimsProcessor(final JwtClaimsProcessor claimsProcessor) {
this.claimsProcessor = claimsProcessor;
}
- @Autowired
- public void setCachedDataProvider(final CachedDataProvider cachedDataProvider) {
+ public void setResourceInfo(final ResourceInfo resourceInfo) {
- this.cachedDataProvider = cachedDataProvider;
+ this.resourceInfo = resourceInfo;
}
}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/MDCInterceptor.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/MDCInterceptor.java
new file mode 100644
index 000000000..a6c8946c7
--- /dev/null
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/MDCInterceptor.java
@@ -0,0 +1,47 @@
+package net.trajano.ms.vertx.jaxrs;
+
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.ext.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import net.trajano.ms.spi.MDCKeys;
+import net.trajano.ms.spi.MicroserviceEngine;
+
+/**
+ * This populates the MDC based on the data available on the request. This will
+ * skip the method and request URI if debug is not enabled.
+ *
+ * @author Archimedes Trajano
+ */
+@Component
+@Provider
+@Priority(Priorities.AUTHORIZATION)
+public class MDCInterceptor implements
+ ContainerRequestFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MDCInterceptor.class);
+
+ @Autowired
+ private MicroserviceEngine engine;
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) {
+
+ MDC.put(MDCKeys.REQUEST_ID, requestContext.getHeaderString(MDCKeys.REQUEST_ID));
+ if (LOG.isDebugEnabled()) {
+ MDC.put(MDCKeys.REQUEST_METHOD, requestContext.getMethod());
+ MDC.put(MDCKeys.REQUEST_URI, requestContext.getUriInfo().getRequestUri().toASCIIString());
+ MDC.put(MDCKeys.HOST, engine.hostname() + ":" + engine.port());
+ }
+
+ }
+
+}
diff --git a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java
index cd45b2618..a5816443c 100644
--- a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java
+++ b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java
@@ -47,7 +47,7 @@ public void setupMapper() {
mapper.setDebugFlags();
final HttpHeaders headers = Mockito.mock(HttpHeaders.class);
Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.WILDCARD_TYPE));
- mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true);
+ mapper.setContextData(headers, Mockito.mock(UriInfo.class), true);
}
@Test
@@ -77,7 +77,7 @@ public void testCheckedHtml() {
final HttpHeaders headers = Mockito.mock(HttpHeaders.class);
Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.TEXT_HTML_TYPE));
- mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true);
+ mapper.setContextData(headers, Mockito.mock(UriInfo.class), true);
final Response response = mapper.toResponse(new IOException("ahem"));
Assert.assertEquals(500, response.getStatus());
@@ -97,7 +97,7 @@ public void testCheckedPlainText() {
final HttpHeaders headers = Mockito.mock(HttpHeaders.class);
Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.TEXT_PLAIN_TYPE));
- mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true);
+ mapper.setContextData(headers, Mockito.mock(UriInfo.class), true);
final Response response = mapper.toResponse(new IOException("ahem"));
Assert.assertEquals(500, response.getStatus());
@@ -110,7 +110,7 @@ public void testCheckedUnsupportedType() {
final HttpHeaders headers = Mockito.mock(HttpHeaders.class);
Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM_TYPE));
- mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true);
+ mapper.setContextData(headers, Mockito.mock(UriInfo.class), true);
final Response response = mapper.toResponse(new IOException("ahem"));
Assert.assertEquals(500, response.getStatus());
diff --git a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java
index 851d0594c..452b81cb2 100644
--- a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java
+++ b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java
@@ -1,17 +1,26 @@
package net.trajano.ms.vertx.test;
+import java.net.URI;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
+
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import net.trajano.ms.core.CryptoOps;
+import net.trajano.ms.sample.Hello;
import net.trajano.ms.vertx.VertxConfig;
import net.trajano.ms.vertx.beans.JwtClaimsProcessor;
import net.trajano.ms.vertx.jaxrs.JwtAssertionInterceptor;
+import net.trajano.ms.vertx.jaxrs.MDCInterceptor;
/**
* Tests are hanging on Travis for some odd reason.
@@ -53,16 +62,37 @@ public Boolean apply(final JwtClaims claims) {
private CryptoOps cryptoOps;
@Autowired
- private JwtAssertionInterceptor interceptor;
+ private JwtAssertionInterceptor jwtInterceptor;
+
+ @Autowired
+ private MDCInterceptor mdcInterceptor;
+ @SuppressWarnings({
+ "unchecked",
+ "rawtypes"
+ })
@Test
public void testInterceptor() throws Exception {
- interceptor.setClaimsProcessor(new ValidatingProcessor("typ", "https://example.com/register"));
+ jwtInterceptor.setClaimsProcessor(new ValidatingProcessor("typ", "https://example.com/register"));
- final JwtClaims jwtClaims = JwtClaims.parse("{\"typ\":\"https://example.com/register\"}");
+ final JwtClaims jwtClaims = JwtClaims.parse("{\"typ\":\"https://example.com/register\", \"aud\":\"sample\", \"jti\": \"abc\", \"iss\":\"http://accounts.trajano.net\"}");
final String jwt = cryptoOps.sign(jwtClaims);
System.out.println(jwt);
+
+ final ResourceInfo resourceInfo = Mockito.mock(ResourceInfo.class);
+ Mockito.when(resourceInfo.getResourceMethod()).thenReturn(Hello.class.getMethod("hello2B"));
+ Mockito.when(resourceInfo.getResourceClass()).thenReturn((Class) Hello.class);
+ jwtInterceptor.setResourceInfo(resourceInfo);
+
+ final ContainerRequestContext containerRequestContext = Mockito.mock(ContainerRequestContext.class);
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ Mockito.when(uriInfo.getRequestUri()).thenReturn(URI.create("http://trajano.net/sample"));
+ Mockito.when(containerRequestContext.getUriInfo()).thenReturn(uriInfo);
+ Mockito.when(containerRequestContext.getHeaderString("X-JWT-Assertion")).thenReturn(jwt);
+ Mockito.when(containerRequestContext.getHeaderString("X-JWT-Audience")).thenReturn("sample");
+ mdcInterceptor.filter(containerRequestContext);
+ jwtInterceptor.filter(containerRequestContext);
// final Request request = mock(Request.class);
// when(request.getHeader("X-JWT-Assertion")).thenReturn(jwt);
// final Response responder = mock(Response.class);
diff --git a/ms-common/pom.xml b/ms-common/pom.xml
index 7dbec9935..20ebf6b30 100644
--- a/ms-common/pom.xml
+++ b/ms-common/pom.xml
@@ -116,6 +116,11 @@
${resteasy.version}
test
+
+ org.slf4j
+ slf4j-jdk14
+ test
+
org.springframework.boot
spring-boot-starter-test
diff --git a/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java b/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java
index f45009f2f..f273fa2c0 100644
--- a/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java
+++ b/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java
@@ -1,6 +1,6 @@
package net.trajano.ms.core;
-import static net.trajano.ms.core.Qualifiers.REQUEST_ID;
+import static net.trajano.ms.spi.MDCKeys.REQUEST_ID;
import java.net.URI;
import java.util.LinkedList;
@@ -18,6 +18,7 @@
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import io.swagger.annotations.ApiModelProperty;
+import net.trajano.ms.spi.MDCKeys;
/**
* Error response. This is the error response that gets built by the system for
@@ -33,6 +34,8 @@
"requestId",
"requestUri",
"threadId",
+ "host",
+ "jwtId",
"stackTrace",
"cause"
})
@@ -135,6 +138,18 @@ public String getMethodName() {
@XmlElement(name = "error_description")
private final String errorDescription;
+ /**
+ * Host of the server that threw the error. This shows the hostname and port.
+ */
+ @XmlElement(name = "host")
+ private final String host;
+
+ /**
+ * JWT ID. This represents the session.
+ */
+ @XmlElement(name = "jwt_id")
+ private final String jwtId;
+
/**
* The request ID. This is obtained from the header.
*/
@@ -175,6 +190,8 @@ protected ErrorResponse() {
errorClass = null;
errorDescription = null;
stackTrace = null;
+ host = null;
+ jwtId = null;
threadId = null;
requestId = null;
requestUri = null;
@@ -192,22 +209,37 @@ protected ErrorResponse() {
public ErrorResponse(final String error,
final String errorDescription) {
- this(error, errorDescription, MDC.get(REQUEST_ID));
- }
-
- public ErrorResponse(final String error,
- final String errorDescription,
- final String requestId) {
-
this.error = error;
this.errorDescription = errorDescription;
- this.requestId = requestId;
+ requestId = MDC.get(REQUEST_ID);
cause = null;
errorClass = null;
+ host = MDC.get(MDCKeys.HOST);
+ jwtId = MDC.get(MDCKeys.JWT_ID);
stackTrace = null;
threadId = Thread.currentThread().getName();
- requestUri = null;
+ requestUri = calculateRequestUri();
+ }
+
+ /**
+ * Creates an error response with just the error code and description. The
+ * request ID will be taken from the MDC.
+ *
+ * @param error
+ * error code
+ * @param errorDescription
+ * error description
+ * @param requestId
+ * ignored
+ * @deprecated the requestId value is ignored and is taken from the MDC.
+ */
+ @Deprecated
+ public ErrorResponse(final String error,
+ final String errorDescription,
+ final String requestId) {
+
+ this(error, errorDescription);
}
/**
@@ -230,6 +262,8 @@ protected ErrorResponse(final Throwable e) {
}
}
error = null;
+ host = null;
+ jwtId = null;
errorClass = e.getClass().getName();
errorDescription = e.getMessage();
threadId = null;
@@ -237,12 +271,47 @@ protected ErrorResponse(final Throwable e) {
requestUri = null;
}
+ /**
+ * Wraps a {@link Throwable} in an {@link ErrorResponse} with full stack trace
+ * and cause if requested.
+ *
+ * @param e
+ * exception to wrap
+ * @param headers
+ * ignored
+ * @param uriInfo
+ * URI info
+ * @param showStackTrace
+ * flag to determine whether the stack trace is to be shown.
+ * @param showRequestUri
+ * ignored
+ * @deprecated use {@link #ErrorResponse(Throwable, UriInfo, boolean)}
+ */
+ @Deprecated
public ErrorResponse(final Throwable e,
final HttpHeaders headers,
final UriInfo uriInfo,
final boolean showStackTrace,
final boolean showRequestUri) {
+ this(e, uriInfo, showStackTrace);
+ }
+
+ /**
+ * Wraps a {@link Throwable} in an {@link ErrorResponse} with full stack trace
+ * and cause if requested.
+ *
+ * @param e
+ * exception to wrap
+ * @param uriInfo
+ * URI info
+ * @param showStackTrace
+ * flag to determine whether the stack trace is to be shown.
+ */
+ public ErrorResponse(final Throwable e,
+ final UriInfo uriInfo,
+ final boolean showStackTrace) {
+
error = ErrorCodes.SERVER_ERROR;
errorDescription = e.getLocalizedMessage();
errorClass = e.getClass().getName();
@@ -263,11 +332,29 @@ public ErrorResponse(final Throwable e,
stackTrace = null;
cause = null;
}
- requestId = headers.getHeaderString(REQUEST_ID);
- requestUri = showRequestUri ? uriInfo.getRequestUri() : null;
+ requestId = MDC.get(REQUEST_ID);
+ host = MDC.get(MDCKeys.HOST);
+ requestUri = calculateRequestUri();
+ jwtId = MDC.get(MDCKeys.JWT_ID);
}
+ /**
+ * Performs a null-check on the request URI data.
+ *
+ * @return request URI
+ */
+ private URI calculateRequestUri() {
+
+ final String requestUriString = MDC.get(MDCKeys.REQUEST_URI);
+ if (requestUriString == null) {
+
+ return null;
+ } else {
+ return URI.create(requestUriString);
+ }
+ }
+
public ErrorResponse getCause() {
return cause;
@@ -288,11 +375,26 @@ public String getErrorDescription() {
return errorDescription;
}
+ public String getHost() {
+
+ return host;
+ }
+
+ public String getJwtId() {
+
+ return jwtId;
+ }
+
public String getRequestId() {
return requestId;
}
+ public URI getRequestUri() {
+
+ return requestUri;
+ }
+
public List getStackTrace() {
return stackTrace;
diff --git a/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java b/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java
index ad2638d37..faf9651b6 100644
--- a/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java
+++ b/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java
@@ -1,6 +1,7 @@
package net.trajano.ms.core;
import net.trajano.ms.spi.CacheNames;
+import net.trajano.ms.spi.MDCKeys;
/**
* Unclassified qualifiers used by the application.
@@ -11,7 +12,7 @@ public final class Qualifiers {
/**
* JWKS Cache Name.
- *
+ *
* @deprecated use {@link net.trajano.ms.spi.CacheNames#JWKS}
*/
@Deprecated
@@ -19,8 +20,11 @@ public final class Qualifiers {
/**
* Request ID HTTP Header.
+ *
+ * @deprecated use {@link net.trajano.ms.spi.MDCKeys#REQUEST_ID}
*/
- public static final String REQUEST_ID = "X-Request-ID";
+ @Deprecated
+ public static final String REQUEST_ID = MDCKeys.REQUEST_ID;
/**
* Roles claim name. The claim is expected to be in a string list format.
diff --git a/ms-common/src/main/java/net/trajano/ms/spi/MDCKeys.java b/ms-common/src/main/java/net/trajano/ms/spi/MDCKeys.java
new file mode 100644
index 000000000..bc5427f5a
--- /dev/null
+++ b/ms-common/src/main/java/net/trajano/ms/spi/MDCKeys.java
@@ -0,0 +1,41 @@
+package net.trajano.ms.spi;
+
+/**
+ * MDC Keys. These are used by the core API. The service implementation should
+ * populate the {@link org.slf4j.MDC} using the following keys
+ */
+public final class MDCKeys {
+
+ /**
+ * Host name and port.
+ */
+ public static final String HOST = "Host";
+
+ /**
+ * JWT ID. Represents a client session;
+ */
+ public static final String JWT_ID = "X-JWT-ID";
+
+ /**
+ * Request ID. Represents a single request coming from the client.
+ */
+ public static final String REQUEST_ID = "X-Request-ID";
+
+ /**
+ * Request method. Represents the method URI being requested upon.
+ */
+ public static final String REQUEST_METHOD = "X-Request-Method";
+
+ /**
+ * Request URI. Represents the URI being requested.
+ */
+ public static final String REQUEST_URI = "X-Request-URI";
+
+ /**
+ * Prevent instantiation of constants class.
+ */
+ private MDCKeys() {
+
+ }
+
+}
diff --git a/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java b/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java
index 7ee8455d7..e9680133e 100644
--- a/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java
+++ b/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java
@@ -10,4 +10,20 @@ public interface MicroserviceEngine {
*/
Object[] bootstrap();
+ /**
+ * Gets the host name of where the engine is running. This may be
+ * null
if the engine has not been initialized.
+ *
+ * @return host name
+ */
+ String hostname();
+
+ /**
+ * Gets the port of where the engine is listening. This may be -1
+ * if the engine has not been initialized.
+ *
+ * @return host name
+ */
+ int port();
+
}
diff --git a/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java b/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java
index 3978335fd..9e50646c5 100644
--- a/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java
+++ b/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java
@@ -6,22 +6,25 @@
import static org.mockito.Mockito.mock;
import java.io.IOException;
+import java.net.URI;
import java.security.GeneralSecurityException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import org.junit.Test;
+import org.slf4j.MDC;
import org.springframework.beans.factory.BeanExpressionException;
import net.trajano.ms.core.ErrorResponse;
+import net.trajano.ms.spi.MDCKeys;
public class ErrorResponseTest {
@Test
public void chainedError() {
- final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(HttpHeaders.class), mock(UriInfo.class), true, false);
+ final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(UriInfo.class), true);
assertNotNull(response.getStackTrace());
assertNotNull(response.getCause());
}
@@ -32,7 +35,7 @@ public void chainedError() {
@Test
public void chainedError2() {
- final ErrorResponse response = new ErrorResponse(new BeanExpressionException("ahem", new IllegalStateException(new GeneralSecurityException())), mock(HttpHeaders.class), mock(UriInfo.class), true, false);
+ final ErrorResponse response = new ErrorResponse(new BeanExpressionException("ahem", new IllegalStateException(new GeneralSecurityException())), mock(UriInfo.class), true);
assertNotNull(response.getStackTrace());
assertNotNull(response.getCause());
}
@@ -40,7 +43,7 @@ public void chainedError2() {
@Test
public void chainedErrorButNoStackTrace() {
- final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(HttpHeaders.class), mock(UriInfo.class), false, false);
+ final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(UriInfo.class), false);
assertNull(response.getStackTrace());
assertNull(response.getCause());
}
@@ -48,30 +51,30 @@ public void chainedErrorButNoStackTrace() {
@Test
public void errorWithNoStackTraces() {
- final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(HttpHeaders.class), mock(UriInfo.class), false, false);
+ final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(UriInfo.class), false);
assertNull(response.getStackTrace());
assertNull(response.getCause());
assertEquals(Thread.currentThread().getName(), response.getThreadId());
assertEquals(IOException.class.getName(), response.getErrorClass());
}
+ @Deprecated
@Test
- public void simpleError() {
+ public void thrownError() {
- final ErrorResponse response = new ErrorResponse("error", "error description", "request id");
- assertEquals("error", response.getError());
- assertEquals("error description", response.getErrorDescription());
- assertEquals("request id", response.getRequestId());
- assertNull(response.getStackTrace());
+ final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(HttpHeaders.class), mock(UriInfo.class), true, false);
+ assertNotNull(response.getStackTrace());
assertNull(response.getCause());
}
@Test
- public void thrownError() {
+ public void thrownErrorWithMDC() {
- final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(HttpHeaders.class), mock(UriInfo.class), true, false);
+ MDC.put(MDCKeys.REQUEST_URI, "http://hello");
+ final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(UriInfo.class), true);
assertNotNull(response.getStackTrace());
assertNull(response.getCause());
+ assertEquals(URI.create("http://hello"), response.getRequestUri());
}
}