[英]JAX-RS — How to return JSON and HTTP status code together?
我正在编写 REST Web 应用程序(NetBeans 6.9、JAX-RS、TopLink Essentials)并尝试返回 JSON和HTTP 状态代码。 我已准备好代码并在从客户端调用 HTTP GET 方法时返回 JSON。 本质上:
@Path("get/id")
@GET
@Produces("application/json")
public M_機械 getMachineToUpdate(@PathParam("id") String id) {
// some code to return JSON ...
return myJson;
}
但我也想用JSON数据一起返回的HTTP状态代码(500,200,204,等)。
我尝试使用HttpServletResponse
:
response.sendError("error message", 500);
但这让浏览器认为这是一个“真实”的 500,因此输出网页是一个常规的 HTTP 500 错误页面。
我想返回一个 HTTP 状态代码,以便我的客户端 JavaScript 可以根据它处理一些逻辑(例如,在 HTML 页面上显示错误代码和消息)。 这是可能的还是不应该将 HTTP 状态代码用于此类事情?
下面是一个例子:
@GET
@Path("retrieve/{uuid}")
public Response retrieveSomething(@PathParam("uuid") String uuid) {
if(uuid == null || uuid.trim().length() == 0) {
return Response.serverError().entity("UUID cannot be blank").build();
}
Entity entity = service.getById(uuid);
if(entity == null) {
return Response.status(Response.Status.NOT_FOUND).entity("Entity not found for UUID: " + uuid).build();
}
String json = //convert entity to json
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
看看Response类。
请注意,您应该始终指定一个内容类型,尤其是当您传递多个内容类型时,但如果每条消息都将表示为 JSON,您只需使用@Produces("application/json")
注释该方法
在 REST Web 服务中设置 HTTP 状态代码有多种用例,至少有一个没有在现有答案中充分记录(即,当您使用 JAXB 使用自动神奇的 JSON/XML 序列化,并且您想返回一个要序列化的对象,但也是一个不同于默认 200 的状态代码)。
因此,让我尝试列举不同的用例和每个用例的解决方案:
当您想要返回不同于200 OK
的状态代码时,最常见的用例是发生错误时。
例如:
在那种情况下,我认为处理问题的最干净的方法是抛出异常。 此异常将由ExceptionMapper
处理,它将异常转换为具有适当错误代码的响应。
您可以使用 Jersey 预先配置的默认ExceptionMapper
(我猜它与其他实现相同)并抛出javax.ws.rs.WebApplicationException
任何现有子类。 这些是预定义的异常类型,它们预先映射到不同的错误代码,例如:
等等。你可以在这里找到列表: API
或者,您可以定义自己的自定义异常和ExceptionMapper
类,并通过@Provider
注释( 本示例的来源)将这些映射器添加到 Jersey:
public class MyApplicationException extends Exception implements Serializable
{
private static final long serialVersionUID = 1L;
public MyApplicationException() {
super();
}
public MyApplicationException(String msg) {
super(msg);
}
public MyApplicationException(String msg, Exception e) {
super(msg, e);
}
}
提供者:
@Provider
public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException>
{
@Override
public Response toResponse(MyApplicationException exception)
{
return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
}
}
注意:您还可以为您使用的现有异常类型编写 ExceptionMappers。
设置状态代码的另一种方法是使用Response
构建器来构建具有预期代码的响应。
在这种情况下,您的方法的返回类型必须是javax.ws.rs.core.Response
。 这在其他各种回应中有所描述,例如他的鲁尼接受的答案,看起来像这样:
@GET
@Path("myresource({id}")
public Response retrieveSomething(@PathParam("id") String id) {
...
Entity entity = service.getById(uuid);
if(entity == null) {
return Response.status(Response.Status.NOT_FOUND).entity("Resource not found for ID: " + uuid).build();
}
...
}
您想要设置返回状态的另一种情况是操作成功时,但您想要返回一个不同于 200 的成功代码,以及您在正文中返回的内容。
一个常见的用例是当您创建一个新实体( POST
请求)并希望返回有关此新实体或实体本身的信息以及201 Created
状态代码时。
一种方法是使用如上所述的响应对象并自己设置请求的主体。 但是,通过这样做,您将失去使用 JAXB 提供的 XML 或 JSON 自动序列化的能力。
这是返回将被 JAXB 序列化为 JSON 的实体对象的原始方法:
@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user){
User newuser = ... do something like DB insert ...
return newuser;
}
这将返回新创建用户的 JSON 表示,但返回状态将是 200,而不是 201。
现在的问题是,如果我想使用Response
构建器来设置返回码,我必须在我的方法中返回一个Response
对象。 我如何仍然返回要序列化的User
对象?
解决此问题的一种方法是获取 servlet 请求对象并自己手动设置响应代码,如 Garett Wilson 的回答中所示:
@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user, @Context final HttpServletResponse response){
User newUser = ...
//set HTTP code to "201 Created"
response.setStatus(HttpServletResponse.SC_CREATED);
try {
response.flushBuffer();
}catch(Exception e){}
return newUser;
}
该方法仍然返回一个实体对象,状态码将为 201。
请注意,为了使其工作,我必须刷新响应。 这是我们漂亮的 JAX_RS 资源中低级 Servlet API 代码的令人不快的复苏,更糟糕的是,它导致标头在此之后无法修改,因为它们已经在线路上发送了。
在这种情况下,最好的解决方案是使用 Response 对象并将实体设置为在此响应对象上进行序列化。 在这种情况下,让 Response 对象通用以指示有效负载实体的类型会很好,但目前情况并非如此。
@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response addUser(User user){
User newUser = ...
return Response.created(hateoas.buildLinkUri(newUser, "entity")).entity(restResponse).build();
}
在这种情况下,我们使用 Response 构建器类的 created 方法将状态代码设置为 201。我们通过 entity() 方法将实体对象(用户)传递给响应。
结果是我们想要的 HTTP 代码是 401,并且响应的主体与我们刚刚返回 User 对象时的 JSON 完全相同。 它还添加了一个位置标题。
Response 类有许多用于不同状态(stati ?)的构建器方法,例如:
Response.accepted() Response.ok() Response.noContent() Response.notAcceptable()
注意: hatoas 对象是我开发的一个帮助类,用于帮助生成资源 URI。 您需要在这里提出自己的机制;)
就是这样。
我希望这个冗长的回复对某人有所帮助:)
hisdrewness 的答案会起作用,但它修改了整个方法,让提供程序(例如 Jackson+JAXB)自动将返回的对象转换为某种输出格式,例如 JSON。 受 Apache CXF 帖子(使用特定于 CXF 的类)的启发,我找到了一种方法来设置应该在任何 JAX-RS 实现中工作的响应代码:注入 HttpServletResponse 上下文并手动设置响应代码。 例如,这里是如何在适当的时候将响应代码设置为CREATED
。
@Path("/foos/{fooId}")
@PUT
@Consumes("application/json")
@Produces("application/json")
public Foo setFoo(@PathParam("fooID") final String fooID, final Foo foo, @Context final HttpServletResponse response)
{
//TODO store foo in persistent storage
if(itemDidNotExistBefore) //return 201 only if new object; TODO app-specific logic
{
response.setStatus(Response.Status.CREATED.getStatusCode());
}
return foo; //TODO get latest foo from storage if needed
}
改进:在找到另一个相关答案后,我了解到可以将HttpServletResponse
作为成员变量注入,即使对于单例服务类(至少在 RESTEasy 中)! 这是比用实现细节污染 API 更好的方法。 它看起来像这样:
@Context //injected response proxy supporting multiple threads
private HttpServletResponse response;
@Path("/foos/{fooId}")
@PUT
@Consumes("application/json")
@Produces("application/json")
public Foo setFoo(@PathParam("fooID") final String fooID, final Foo foo)
{
//TODO store foo in persistent storage
if(itemDidNotExistBefore) //return 201 only if new object; TODO app-specific logic
{
response.setStatus(Response.Status.CREATED.getStatusCode());
}
return foo; //TODO get latest foo from storage if needed
}
如果你想让你的资源层没有Response
对象,那么我建议你使用@NameBinding
并绑定到ContainerResponseFilter
实现。
这是注释的主要内容:
package my.webservice.annotations.status;
import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Status {
int CREATED = 201;
int value();
}
这是过滤器的主要内容:
package my.webservice.interceptors.status;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
@Provider
public class StatusFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
if (containerResponseContext.getStatus() == 200) {
for (Annotation annotation : containerResponseContext.getEntityAnnotations()) {
if(annotation instanceof Status){
containerResponseContext.setStatus(((Status) annotation).value());
break;
}
}
}
}
}
然后你的资源上的实现就变成了:
package my.webservice.resources;
import my.webservice.annotations.status.StatusCreated;
import javax.ws.rs.*;
@Path("/my-resource-path")
public class MyResource{
@POST
@Status(Status.CREATED)
public boolean create(){
return true;
}
}
如果您因为异常而想要更改状态代码,使用 JAX-RS 2.0,您可以像这样实现一个 ExceptionMapper。 这为整个应用程序处理这种异常。
@Provider
public class UnauthorizedExceptionMapper implements ExceptionMapper<EJBAccessException> {
@Override
public Response toResponse(EJBAccessException exception) {
return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build();
}
}
如果您的 WS-RS 需要引发错误,为什么不直接使用 WebApplicationException?
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Path("{id}")
public MyEntity getFoo(@PathParam("id") long id, @QueryParam("lang")long idLanguage) {
if (idLanguage== 0){
// No URL parameter idLanguage was sent
ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);
builder.entity("Missing idLanguage parameter on request");
Response response = builder.build();
throw new WebApplicationException(response);
}
... //other stuff to return my entity
return myEntity;
}
我发现用重复的代码构建一个 json 消息非常有用,如下所示:
@POST
@Consumes("application/json")
@Produces("application/json")
public Response authUser(JsonObject authData) {
String email = authData.getString("email");
String password = authData.getString("password");
JSONObject json = new JSONObject();
if (email.equalsIgnoreCase(user.getEmail()) && password.equalsIgnoreCase(user.getPassword())) {
json.put("status", "success");
json.put("code", Response.Status.OK.getStatusCode());
json.put("message", "User " + authData.getString("email") + " authenticated.");
return Response.ok(json.toString()).build();
} else {
json.put("status", "error");
json.put("code", Response.Status.NOT_FOUND.getStatusCode());
json.put("message", "User " + authData.getString("email") + " not found.");
return Response.status(Response.Status.NOT_FOUND).entity(json.toString()).build();
}
}
JAX-RS 支持标准/自定义 HTTP 代码。 参见 ResponseBuilder 和 ResponseStatus,例如:
请记住,JSON 信息更多地是关于与资源/应用程序关联的数据。 HTTP 代码更多地与所请求的 CRUD 操作的状态有关。 (至少在 REST-ful 系统中应该是这样)
请看这里的例子,它最好地说明了这个问题以及它在最新的(2.3.1)版本的 Jersey 中是如何解决的。
https://jersey.java.net/documentation/latest/representations.html#d0e3586
它基本上涉及定义自定义异常并将返回类型保留为实体。 有错误时抛出异常,否则返回POJO。
我没有使用 JAX-RS,但我有一个类似的场景,我使用:
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
另外,请注意,默认情况下,如果 http 代码为 400 或更多,Jersey 将覆盖响应正文。
为了让您指定的实体作为响应正文,请尝试将以下 init-param 添加到您的 web.xml 配置文件中的 Jersey 中:
<init-param>
<!-- used to overwrite default 4xx state pages -->
<param-name>jersey.config.server.response.setStatusOverSendError</param-name>
<param-value>true</param-value>
</init-param>
以下代码对我有用。 通过带注释的 setter 注入 messageContext 并在我的“add”方法中设置状态代码。
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.apache.cxf.jaxrs.ext.MessageContext;
public class FlightReservationService {
MessageContext messageContext;
private final Map<Long, FlightReservation> flightReservations = new HashMap<>();
@Context
public void setMessageContext(MessageContext messageContext) {
this.messageContext = messageContext;
}
@Override
public Collection<FlightReservation> list() {
return flightReservations.values();
}
@Path("/{id}")
@Produces("application/json")
@GET
public FlightReservation get(Long id) {
return flightReservations.get(id);
}
@Path("/")
@Consumes("application/json")
@Produces("application/json")
@POST
public void add(FlightReservation booking) {
messageContext.getHttpServletResponse().setStatus(Response.Status.CREATED.getStatusCode());
flightReservations.put(booking.getId(), booking);
}
@Path("/")
@Consumes("application/json")
@PUT
public void update(FlightReservation booking) {
flightReservations.remove(booking.getId());
flightReservations.put(booking.getId(), booking);
}
@Path("/{id}")
@DELETE
public void remove(Long id) {
flightReservations.remove(id);
}
}
使用Microprofile OpenAPI扩展Nthalk的答案,您可以使用@APIResponse注释将返回代码与您的文档对齐。
这允许标记 JAX-RS 方法,如
@GET
@APIResponse(responseCode = "204")
public Resource getResource(ResourceRequest request)
您可以使用ContainerResponseFilter解析此标准化注释
@Provider
public class StatusFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
if (responseContext.getStatus() == 200) {
for (final var annotation : responseContext.getEntityAnnotations()) {
if (annotation instanceof APIResponse response) {
final var rawCode = response.responseCode();
final var statusCode = Integer.parseInt(rawCode);
responseContext.setStatus(statusCode);
}
}
}
}
}
当您在方法上放置多个注释时会出现一个警告,例如
@APIResponse(responseCode = "201", description = "first use case")
@APIResponse(responseCode = "204", description = "because you can")
public Resource getResource(ResourceRequest request)
我正在将 jersey 2.0 与消息正文读取器和写入器一起使用。 我将我的方法返回类型作为一个特定实体,它也用于消息正文编写器的实现,并且我返回了相同的 pojo,一个 SkuListDTO。 @GET @Consumes({"application/xml", "application/json"}) @Produces({"application/xml", "application/json"}) @Path("/skuResync")
public SkuResultListDTO getSkuData()
....
return SkuResultListDTO;
我所做的只是改变了这一点,我将编写器的实现放在一边,但它仍然有效。
public Response getSkuData()
...
return Response.status(Response.Status.FORBIDDEN).entity(dfCoreResultListDTO).build();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.