I am working on an application that consists of several backend services and a frontend client. The entire application is written in Java, and we use the Apache TomEE webserver to run it.
The backend services expose several APIs, and contain several controllers. Some of these APIs are accessible to the frontend client, and some are for internal communication between backend services.
Logging is very important for this application. There is a requirement that the logging system is always initialized before commencing normal operations (to ensure full traceability). The application uses a secure logging system that requires a key to initialize the logging (the logs are signed using this key to prevent tampering of the logs). There is also a requirement that the logging key should be uploaded to each service. Each backend service has an endpoint for receiving a logging key.
There is a "chicken or egg" type problem. The application needs to be running to receive the key, but also the application should not be fully operational until the key has been received.
To meet the requirements, we are considering the following startup procedure:
Is there a standard way of activating endpoints to facilitate this startup process? or anyway of controlling access to endpoints.
Some extra information: the controllers classes within the application do not extend any other class, and are only decorated with the @Path
and @Stateless
annotations.
I followed the approach of using a filter (as suggested by Bogdan below). I have created a filter that captures all requests. The application starts up correctly. The init()
method in the filter class is called. But when I access the /installkey
endpoint an error occurs.
What seems to happen is that the doFilter(ServletRequest, ServletResponse, FilterChain)
method is being called, and my code detects that the request is for the /installkey
endpoint. But an error comes from the call: filterChain.doFilter(request, response);
.
I have checked, and I know that the variable filterChain
is not null
, however within the method doFilter(ServletRequest, ServletResponse, FilterChain)
something goes wrong and I cant debug it.
Possibly, I didn't initialized something that needs to be initialized.
I have added the output that I get below.
Now I have the following in my web.xml
:
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.company.filter.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
And the following class:
public class LoggingFilter implements Filter {
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
public void doFilter(final ServletRequest request,
final ServletResponse response,
final FilterChain filterChain) throws IOException,
ServletException {
String url = "";
if (request instanceof HttpServletRequest) {
url = ((HttpServletRequest) request).getRequestURL().toString();
}
if (url.endsWith("/installkey/")) {
filterChain.doFilter(request, response);
return;
} else if (loggerConfig.isInitialized()) {
filterChain.doFilter(request, response);
return;
}
}
public void destroy() {
System.out.println("XXXXXXXXXXX Running destroy");
}
}
But I get the following error:
Jan 19, 2016 10:42:25 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [default] in context with path [/vw-ws-rest] threw exception [Error processing webservice request] with root cause
java.lang.NullPointerException
at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:240)
at org.apache.openejb.server.cxf.rs.CxfRsHttpListener.doInvoke(CxfRsHttpListener.java:227)
at org.apache.tomee.webservices.CXFJAXRSFilter.doFilter(CXFJAXRSFilter.java:94)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at com.company.filter.LoggingFilter.doFilter(LoggingFilter.java:63)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.tomee.catalina.OpenEJBValve.invoke(OpenEJBValve.java:44)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
As an alternative, I tried the approach of using JAX-RS name binding, as suggested by Cássio Mazzochi Molin.
I created the interface:
import javax.ws.rs.NameBinding;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD })
public @interface TemporarilyDisabled {
}
And I created a filter class as follows:
@Provider
@TemporarilyDisabled
public class LoggingFilter implements ContainerRequestFilter {
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
System.out.println("in filter method!");
}
}
And updated my resource controller class as follows:
@Path("installkey")
@Stateless(name = "vw-installKeyResource")
public class VwInstallKeyResource {
@Inject
private Logger LOG;
@EJB
//... some required classes
@POST
@TemporarilyDisabled
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response savePlatformData(final InstallKeyData installKeyData)
throws CryptographicOperationException, DuplicateEntryException {
....
}
}
This application is using Java EE 6, which I cant update. To test this approach, I had to add the following dependency to the application:
<!-- JAX-RS -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0</version>
</dependency>
The code all compiles ok, and the application starts up ok.
But when i access the endpoint (the endpoint that should be caught by the filter), then the filter code is not executed (I never see the print statement in the filter method), and the endpoint is simply executed as normal.
For some reason, the filter is not capturing the request.
I don't know if the problem is related to the fact that the endpoint is a POST. Alternatively, possibly JAX-RS does not find the filter class, it is decorated with @provider, but I dont know if I need to register the filter in any other way.
A REST Service Endpoint is an endpoint which services a set of REST resources.
When an API interacts with another system, the touchpoints of this communication are considered endpoints. For APIs, an endpoint can include a URL of a server or service. Each endpoint is the location from which APIs can access the resources they need to carry out their function.
I don't think you will find any out of the box solution for it.
When using JAX-RS 2.0 and its implementations, you will find some great resources: you could use a name binding filter to abort the request to a certain endpoint, based on your conditions.
What is cool in this approach is that you can keep your endpoints lean and focused on their business logic. The logic responsible for aborting the request will be in a filter. To temporarily disable one or more endpoints, you'll just have to place an annotation on them. It will activate the filter that prevents the request from reaching the endpoint. All endpoints are enabled by default and you will selectively disable the ones you don't want to receive requests.
To bind filters to your REST endpoints, JAX-RS 2.0 provides the meta-annotation @NameBinding
. It can be used to create other annotations that will be used to bind filters to your JAX-RS endpoints.
Consider the @TemporarilyDisabled
annotation defined below. It's annotated with @NameBinding
:
@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface TemporarilyDisabled { }
The @TemporarilyDisabled
annotation created above will be used to decorate a filter class, which implements ContainerRequestFilter
, allowing you to abort the request:
@Provider
@TemporarilyDisabled
public class TemporarilyDisableEndpointRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (isEndpointTemporarilyDisabled) {
requestContext.abortWith(Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity("Service temporarily unavailable.")
.build());
}
}
}
The @Provider
annotation marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider scanning phase.
You can write any condition to test if your endpoint should be temporarily disabled or not.
In the example above:
If the isEndpointTemporarilyDisabled
condition is evaluated to true
, the request will be aborted with a HTTP 503 Service Unavailable
response.
If the isEndpointTemporarilyDisabled
is evaluated to false
, the request won't be aborted and will reach the endpoint the user requested.
503 Service Unavailable
response?According to the RFC 2616, the HTTP 503 Service Unavailable
should be used in the following situations:
10.5.4 503 Service Unavailable
The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. The implication is that this is a temporary condition which will be alleviated after some delay. If known, the length of the delay MAY be indicated in a
Retry-After
header. If noRetry-After
is given, the client SHOULD handle the response as it would for a500
response.Note: The existence of the
503
status code does not imply that a server must use it when becoming overloaded. Some servers may wish to simply refuse the connection.
To bind the filter to your endpoints methods or classes, annotate them with the @TemporarilyDisabled
annotation defined above. For the methods and/or classes which are annotated, the filter will be executed:
@Path("/")
public class MyEndpoint {
@GET
@Path("{id}")
@Produces("application/json")
public Response myMethod(@PathParam("id") Long id) {
// This method is not annotated with @TemporarilyDisabled
// The request filter won't be executed when invoking this method
...
}
@DELETE
@Path("{id}")
@TemporarilyDisabled
@Produces("application/json")
public Response myTemporarilyDisabledMethod(@PathParam("id") Long id) {
// This method is annotated with @TemporarilyDisabled
// The request filter will be executed when invoking this method
...
}
}
In the example above, the request filter will be executed only for the myTemporarilyDisabledMethod(Long)
method because it's annotated with @TemporarilyDisabled
.
You could activate all the endpoints but disallow access until the key is received. You could do that by placing a Servlet Filter in front of all your endpoints that look for some flag that you set up after you activate the key.
If the flag is set (meaning the incoming key was set up successfully) then you allow access to the endpoints, otherwise from the filter you return a status of some sort (401 or 403). If you set that flag in memory or somewhere fast to read the performance overhead of the filter should be small enough to be ignored.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With