Jersey 在使用 ContainerRequestContext.abortWith() 后为每次调用抛出 NPE
Jersey throws NPE for each call after using ContainerRequestContext.abortWith()
我有一个 RESTful 端点,我通过自定义 ContainerRequestFilter 通过简单的授权检查来保护它。过滤器检查 HTTP 会话中包含的所有信息是否正确,如果不正确,则执行此操作:
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.entity("Forbidden").build());
一切都很好,很漂亮。奇怪的是,当我再次发出相同的 GET 请求时,Jersey 服务器报告 NPE 并且没有 return 任何东西。
NPE 堆栈跟踪:
Jul 20, 2016 5:27:53 PM org.glassfish.jersey.server.ServerRuntime$Responder writeResponse
SEVERE: An I/O error has occurred while writing a response message entity to the container output stream.
java.lang.IllegalStateException: The output stream has already been closed.
at org.glassfish.jersey.message.internal.CommittingOutputStream.setStreamProvider(CommittingOutputStream.java:147)
at org.glassfish.jersey.message.internal.OutboundMessageContext.setStreamProvider(OutboundMessageContext.java:803)
at org.glassfish.jersey.server.ContainerResponse.setStreamProvider(ContainerResponse.java:372)
at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:694)
at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444)
at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434)
at org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:329)
at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:384)
at org.glassfish.grizzly.http.server.HttpHandler.run(HttpHandler.java:224)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:591)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:571)
at java.lang.Thread.run(Thread.java:745)
这是怎么回事?我不想关闭输出流。我只需要一个方法来 return 一个 http return 代码 + 消息给请求者。
万一有人遇到我遇到的同样问题,这里是答案:
您不能重复使用回复!输出流编写器的实例绑定到 Request,一旦您在 abortWith() 中使用它,流将永远提交。因此,它不能再用于发送响应。
您需要从 filter() 方法中初始化一个新请求。
简单来说,不要使用这样的结构:
private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN)
.entity("Access blocked for all users !!").build();
顺便说一句,我在这里找到了这段损坏的代码:
http://howtodoinjava.com/jersey/jersey-rest-security/
所以在实现这个 RequestFilter 时要小心。
我完全同意。我从 RESTful Java with JAX-RS 2.0, 2nd Edition 中找到了这个示例,经过一个下午的挖掘后我发现它很有用。
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.NotAuthorizedException;
@Provider
@PreMatching
public class BearerTokenFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext ctx) throws IOException {
String authHeader = request.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) throw new NotAuthorizedException("Bearer");
String token = parseToken(authHeader);
if (verifyToken(token) == false) {
throw new NotAuthorizedException("Bearer error=\"invalid_token\"");
}
}
private String parseToken(String header) {...}
private boolean verifyToken(String token) {...}
}
"In this example, if there is no Authorization header or it is invalid, the request is aborted
with a NotAuthorizedException. The client receives a 401 response with a WWW-Authenticate
header set to the value passed into the constructor of NotAuthorizedException. If you want to avoid exception mapping, then you can use the ContainerRequestContext.abortWith() method instead. Generally, however, I prefer to throw exceptions."
我想,这就是作者更喜欢抛出异常的原因。
如前所述,问题出在已经设置的静态 ACCESS_DENIED 响应上。
private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build();
所以替换:
requestContext.abortWith(ACCESS_DENIED)
来自
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build());
您不需要在过滤方法中使用 requestContext.abortWith()。相反,请执行以下操作。
String authCredentials = containerRequest.getHeaderString(HttpHeaders.AUTHORIZATION);
从您的 REST 客户端 [browser/postman/SoapUI/jersey 客户端程序] 发送的 header 将采用 "Basic encodedString" 格式。可以使用 java.util.Base64
轻松解码 encodedString。服务器端解码后,字符串格式为username:password。使用String.split(":");
分隔用户名和密码。继续验证。
如果验证结果为假,则从您在此处覆盖的 ContainerRequestFilter
的过滤器方法中抛出 WebApplicationException
。
我有一个 RESTful 端点,我通过自定义 ContainerRequestFilter 通过简单的授权检查来保护它。过滤器检查 HTTP 会话中包含的所有信息是否正确,如果不正确,则执行此操作:
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.entity("Forbidden").build());
一切都很好,很漂亮。奇怪的是,当我再次发出相同的 GET 请求时,Jersey 服务器报告 NPE 并且没有 return 任何东西。
NPE 堆栈跟踪:
Jul 20, 2016 5:27:53 PM org.glassfish.jersey.server.ServerRuntime$Responder writeResponse
SEVERE: An I/O error has occurred while writing a response message entity to the container output stream.
java.lang.IllegalStateException: The output stream has already been closed.
at org.glassfish.jersey.message.internal.CommittingOutputStream.setStreamProvider(CommittingOutputStream.java:147)
at org.glassfish.jersey.message.internal.OutboundMessageContext.setStreamProvider(OutboundMessageContext.java:803)
at org.glassfish.jersey.server.ContainerResponse.setStreamProvider(ContainerResponse.java:372)
at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:694)
at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444)
at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434)
at org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:329)
at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:384)
at org.glassfish.grizzly.http.server.HttpHandler.run(HttpHandler.java:224)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:591)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:571)
at java.lang.Thread.run(Thread.java:745)
这是怎么回事?我不想关闭输出流。我只需要一个方法来 return 一个 http return 代码 + 消息给请求者。
万一有人遇到我遇到的同样问题,这里是答案: 您不能重复使用回复!输出流编写器的实例绑定到 Request,一旦您在 abortWith() 中使用它,流将永远提交。因此,它不能再用于发送响应。
您需要从 filter() 方法中初始化一个新请求。
简单来说,不要使用这样的结构:
private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN)
.entity("Access blocked for all users !!").build();
顺便说一句,我在这里找到了这段损坏的代码: http://howtodoinjava.com/jersey/jersey-rest-security/ 所以在实现这个 RequestFilter 时要小心。
我完全同意。我从 RESTful Java with JAX-RS 2.0, 2nd Edition 中找到了这个示例,经过一个下午的挖掘后我发现它很有用。
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.NotAuthorizedException;
@Provider
@PreMatching
public class BearerTokenFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext ctx) throws IOException {
String authHeader = request.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) throw new NotAuthorizedException("Bearer");
String token = parseToken(authHeader);
if (verifyToken(token) == false) {
throw new NotAuthorizedException("Bearer error=\"invalid_token\"");
}
}
private String parseToken(String header) {...}
private boolean verifyToken(String token) {...}
}
"In this example, if there is no Authorization header or it is invalid, the request is aborted with a NotAuthorizedException. The client receives a 401 response with a WWW-Authenticate header set to the value passed into the constructor of NotAuthorizedException. If you want to avoid exception mapping, then you can use the ContainerRequestContext.abortWith() method instead. Generally, however, I prefer to throw exceptions."
我想,这就是作者更喜欢抛出异常的原因。
如前所述,问题出在已经设置的静态 ACCESS_DENIED 响应上。
private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build();
所以替换:
requestContext.abortWith(ACCESS_DENIED)
来自
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build());
您不需要在过滤方法中使用 requestContext.abortWith()。相反,请执行以下操作。
String authCredentials = containerRequest.getHeaderString(HttpHeaders.AUTHORIZATION);
从您的 REST 客户端 [browser/postman/SoapUI/jersey 客户端程序] 发送的 header 将采用 "Basic encodedString" 格式。可以使用 java.util.Base64
轻松解码 encodedString。服务器端解码后,字符串格式为username:password。使用String.split(":");
分隔用户名和密码。继续验证。
如果验证结果为假,则从您在此处覆盖的 ContainerRequestFilter
的过滤器方法中抛出 WebApplicationException
。