我正在开发一个由多个后端服务和前端客户端组成的应用程序。整个应用程序是用Java编写的,我们使用Apache TomEE Web服务器来运行它。
后端服务公开了几个API,并包含多个控制器。其中一些API可供前端客户端访问,一些API用于后端服务之间的内部通信。
记录对于此应用程序非常重要。要求在开始正常操作之前始终初始化日志记录系统(以确保完全可追溯性)。该应用程序使用安全日志记录系统,该系统需要密钥来初始化日志记录(使用此密钥对日志进行签名以防止篡改日志)。还需要将日志记录密钥上载到每个服务。每个后端服务都有一个端点,用于接收日志密钥。
存在“鸡或蛋”类型的问题。应用程序需要运行才能接收密钥,但在收到密钥之前,应用程序也不应完全正常运行。
为了满足要求,我们正在考虑以下启动程序:
是否有一种激活端点的标准方法来促进此启动过程?或者无论如何控制对端点的访问。
一些额外信息:应用程序中的控制器类不扩展任何其他类,并且仅使用@Path
和@Stateless
注释进行修饰。
我遵循使用过滤器的方法(如下面的Bogdan所建议的)。我创建了一个捕获所有请求的过滤器。应用程序正确启动。调用过滤器类中的init()
方法。但是,当我访问/installkey
端点时,会发生错误。
似乎发生的是调用doFilter(ServletRequest, ServletResponse, FilterChain)
方法,我的代码检测到请求是针对/installkey
端点的。但是来自通话的错误:filterChain.doFilter(request, response);
。
我已经检查过了,我知道变量filterChain
不是null
,但是在doFilter(ServletRequest, ServletResponse, FilterChain)
方法中出现问题我无法调试它。
可能,我没有初始化需要初始化的东西。
我添加了下面的输出。
现在我的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>
以下课程:
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");
}
}
但是我收到以下错误:
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)
作为替代方案,我尝试了使用JAX-RS名称绑定的方法,正如CássioMazzochiMolin所建议的那样。
我创建了界面:
import javax.ws.rs.NameBinding;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD })
public @interface TemporarilyDisabled {
}
我创建了一个过滤器类,如下所示:
@Provider
@TemporarilyDisabled
public class LoggingFilter implements ContainerRequestFilter {
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
System.out.println("in filter method!");
}
}
并更新了我的资源控制器类,如下所示:
@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 {
....
}
}
此应用程序使用的是Java EE 6,我无法更新。为了测试这种方法,我不得不在应用程序中添加以下依赖项:
<!-- JAX-RS -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0</version>
</dependency>
代码全部编译好,应用程序启动正常。
但是当我访问端点(应该被过滤器捕获的端点)时,则不执行过滤器代码(我从未在过滤器方法中看到print语句),并且端点只是正常执行。
由于某种原因,过滤器未捕获请求。
我不知道问题是否与端点是POST的事实有关。或者,可能JAX-RS没有找到过滤器类,它用@provider修饰,但我不知道是否需要以任何其他方式注册过滤器。
答案 0 :(得分:4)
我认为您不会找到任何开箱即用解决方案。
使用JAX-RS 2.0及其实现时,您会发现一些很棒的资源:您可以使用名称绑定过滤器根据您的条件中止对某个端点的请求。
这种方法的优点在于,您可以保持端点的精益,并专注于业务逻辑。负责中止请求的逻辑将在过滤器中。要暂时禁用一个或多个端点,您只需在其上放置注释即可。它将激活阻止请求到达端点的过滤器。默认情况下启用所有端点,您将有选择地禁用您不希望接收请求的端点。
为了将过滤器绑定到REST端点,JAX-RS 2.0提供了元注释@NameBinding
。它可用于创建其他注释,用于将过滤器绑定到JAX-RS端点。
考虑下面定义的@TemporarilyDisabled
注释。它注明了@NameBinding
:
@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface TemporarilyDisabled { }
上面创建的@TemporarilyDisabled
注释将用于修饰过滤器类,它实现ContainerRequestFilter
,允许您中止请求:
@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
注释标记了在提供程序扫描阶段JAX-RS运行时应该可以发现的扩展接口的实现。
您可以编写任何条件来测试您的终端是否应暂时禁用。
在上面的示例中:
如果isEndpointTemporarilyDisabled
条件评估为true
,则请求将以HTTP 503 Service Unavailable
响应中止。
如果isEndpointTemporarilyDisabled
被评估为false
,请求将不会中止,并且会到达用户请求的端点。
503 Service Unavailable
响应中止请求?根据RFC 2616,在以下情况下应使用HTTP 503 Service Unavailable
:
10.5.4 503服务不可用
由于a,服务器当前无法处理请求 临时超载或维护服务器。暗示 这是一个临时条件,将在之后得到缓解 有些延迟。如果已知,延迟的长度可以用a表示
Retry-After
标题。如果没有给出Retry-After
,客户端应该是 处理响应,就像处理500
响应一样。注意:
503
状态代码的存在并不意味着a 服务器必须在变得过载时使用它。有些服务器可能希望 简单地拒绝连接。
要将过滤器绑定到端点方法或类,请使用上面定义的@TemporarilyDisabled
注释对其进行注释。对于注释的方法和/或类,将执行过滤器:
@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
...
}
}
在上面的示例中,请求过滤器仅针对myTemporarilyDisabledMethod(Long)
方法执行,因为它使用@TemporarilyDisabled
进行了注释。
答案 1 :(得分:3)
您可以激活所有端点,但在收到密钥之前禁止访问。您可以通过在所有端点前放置Servlet Filter来查找激活密钥后设置的标记。
如果设置了标志(意味着传入密钥已成功设置),则允许访问端点,否则从过滤器返回某种状态(401或403)。如果你在内存或某个地方快速设置该标志,那么过滤器的性能开销应该小到可以忽略。