How to selectively inactivate REST endpoints in a Java application?

I am working on an application consisting of several backend services and an external client. The entire application is written in Java, and we use the Apache TomEE web server to run it.

Backend services provide several APIs and contain several controllers. Some of these APIs are available to an external client, and some of them are designed for internal communication between backend services.

Logging is very important for this application. There is a requirement that the registration system always be initialized before the start of normal operations (to ensure complete traceability). The application uses a secure logging system for which it is necessary to initialize the logbook (logs are blocked with this key to prevent unauthorized use of logs). It is also necessary that the registration key is uploaded to each service. Each core service has an endpoint for receiving a registration key.

There is a chicken or egg problem. The application must be running to receive the key, but also the application should not fully function until the key is received.

To meet the requirements, we consider the following startup procedure:

  • Starting backend services in a reduced mode of operation, in which the only available endpoint in each service is the one that accepts the incoming key.
  • After receiving the key and initializing the logging system, then activate the rest of the endpoints and begin the usual operations.

Is there a standard way to activate endpoints to facilitate this startup process? or in any case, control access to endpoints.

Additional information: the controller classes in the application do not apply to other classes and are only decorated with @Path and @Stateless .


Update 1

I followed the approach to using a filter (as suggested by Bogdan below). I created a filter that captures all requests. The application starts correctly. The init() method is called in the filter class. But when I access the /installkey , an error occurs.

It looks like the doFilter(ServletRequest, ServletResponse, FilterChain) method doFilter(ServletRequest, ServletResponse, FilterChain) is being called, and my code detects that the request for the endpoint is /installkey . But when calling, an error occurs: filterChain.doFilter(request, response); .

I checked, and I know that the filterChain variable filterChain not null , however, inside the doFilter(ServletRequest, ServletResponse, FilterChain) method doFilter(ServletRequest, ServletResponse, FilterChain) something goes wrong and I can not debug it.

Perhaps I did not initialize something that needs to be initialized.

I have added the result that I get below.

Now I have the following in 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) 

Update 2

As an alternative, I tried using the JAX-RS name binding, as suggested by Cassio Mazzotchi Molin.

I created an 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 the 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 uses Java EE 6, which I cannot 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 fine, and the application starts fine.

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 just executes as usual.

For some reason, the filter does not capture the request.

I don't know if the problem is that the endpoint is POST. Alternatively, maybe JAX-RS does not find the filter class, it is decorated with @provider, but I do not know if I need to register the filter in any other way.

+6
source share
2 answers

I do not think that you will find any of the ready-made solutions for him.

When using JAX-RS 2.0 and its implementations, you will find several useful resources: you can use the name binding filter to interrupt the request for a specific endpoint based on your conditions.

What's cool about this approach is that you can support endpoints and focus on their business logic. The logic responsible for interrupting the request will be in the filter. To temporarily disable one or more endpoints, you just need to place an annotation on them. It activates a filter that prevents the request from reaching the endpoint. All endpoints are enabled by default, and you selectively disable those that you don’t want to receive.

Name Binding Annotation Definition

To bind filters to your REST endpoints, JAX-RS 2.0 provides the @NameBinding meta annotation. You can use it to create other annotations that will be used to bind filters to your JAX-RS endpoints.

Consider the @TemporarilyDisabled annotation defined below. It is annotated using @NameBinding :

 @NameBinding @Retention(RUNTIME) @Target({TYPE, METHOD}) public @interface TemporarilyDisabled { } 

Prevent HTTP request to reach your endpoints

The @TemporarilyDisabled annotation created above will be used to decorate the filter class that implements ContainerRequestFilter , which allows you to cancel 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()); } } } 

@Provider denotes an implementation of the extension interface that must be detected at JAX-RS runtime during the provider scan phase.

You can write any condition to check if your endpoint will be temporarily disabled or not.

In the above example:

  • If the condition isEndpointTemporarilyDisabled evaluates to true , the request will be aborted with an HTTP 503 Service Unavailable response.

  • If the value isEndpointTemporarilyDisabled evaluates to false , the request will not be interrupted and will reach the endpoint requested by the user.

Why interrupt a request with a 503 Service Unavailable response?

In accordance with RFC 2616 , HTTP 503 Service Unavailable should be used in the following situations:

10.5.4 503 Service unavailable

Currently, the server cannot process the request due to temporary overload or server maintenance . The implication is that this is a temporary condition that will be mitigated after some delays. If known, the delay length MAY be specified in Retry-After . If there is no Retry-After , the client MUST process the response, as it would for a 500 response.

Note. The existence of a 503 status code does not mean that the server should use it during reboot. Some servers may wish to simply refuse the connection .

Associating a filter with endpoints

To bind a filter to your methods or endpoint classes, annotate them using the @TemporarilyDisabled annotation defined above. For methods and / or classes that 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 above example, the query filter will only be executed for the myTemporarilyDisabledMethod(Long) method, since it is annotated with @TemporarilyDisabled .

+3
source

You can activate all endpoints, but deny access until a key is received. You can do this by placing a Servlet Filter in front of all your endpoints that are looking for some flag that you configured after activating the key.

If the flag is set (which means that the input key was configured successfully), you allow access to the endpoints, otherwise you will return a status of a certain type from the filter (401 or 403). If you set this flag in memory or somewhere fast, to read the filter performance overhead should be small enough to be ignored.

+2
source

All Articles