JAX-RS — 如何同时返回 JSON 和 HTTP 状态代码?1. 错误代码 (500, 404,...)2. 成功,但不是200

2022-08-31 05:22:21

我正在编写一个 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;
}

我也想返回HTTP状态代码(500,200,204等)以及JSON数据。

我试图使用:HttpServletResponse

response.sendError("error message", 500);

但这让浏览器认为这是一个“真正的”500,所以输出网页是一个常规的HTTP 500错误页面。

我想返回一个HTTP状态代码,以便我的客户端JavaScript可以根据它处理一些逻辑(例如,在HTML页面上显示错误代码和消息)。这是否可能,或者HTTP状态代码不应该用于这样的事情?


答案 1

下面是一个示例:

@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();
}

查看响应类。

请注意,应始终指定内容类型,尤其是在传递多个内容类型时,但如果每条消息都表示为 JSON,则只需使用@Produces("application/json")


答案 2

在 REST Web 服务中设置 HTTP 状态代码有几个用例,至少有一个用例在现有答案中没有充分记录(即,当您使用 JAXB 的自动魔术 JSON/XML 序列化时,并且您希望返回要序列化的对象,但同时返回与默认状态代码 200 不同的状态代码)。

因此,让我尝试枚举不同的用例和每个用例的解决方案:

1. 错误代码 (500, 404,...)

当您想要返回与发生错误不同的状态代码时,最常见的用例是发生错误时。200 OK

例如:

  • 请求实体但实体不存在 (404)
  • 请求语义不正确 (400)
  • 用户未获得授权 (401)
  • 数据库连接有问题 (500)
  • 等。。

a) 引发异常

在这种情况下,我认为处理问题的最干净方法是抛出一个异常。此异常将由 处理,该 将异常转换为具有相应错误代码的响应。ExceptionMapper

您可以使用泽西岛预配置的默认值(我猜它与其他实现相同),并抛出任何现有的子类。这些是预定义的异常类型,预先映射到不同的错误代码,例如:ExceptionMapperjavax.ws.rs.WebApplicationException

  • BadRequestException (400)
  • 内部服务器错误异常 (500)
  • 不成立异常 (404)

等。你可以在这里找到列表:API

或者,您可以定义自己的自定义例外和类,并通过注释(此示例的来源)将这些映射器添加到 Jersey:ExceptionMapper@Provider

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();  
        }
    }

注意:您还可以为使用的现有异常类型编写异常映射程序。

b) 使用响应构建器

设置状态代码的另一种方法是使用生成器使用预期代码构建响应。Response

在这种情况下,方法的返回类型必须为 。这在各种其他响应中都有描述,例如hisrowness的接受答案,看起来像这样: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();
    }
    ...
}

2. 成功,但不是200

要设置返回状态的另一种情况是,操作成功,但要返回不同于 200 的成功代码,以及正文中返回的内容。

一个常见的用例是,当您创建一个新实体(请求)并希望返回有关此新实体或实体本身的信息以及状态代码时。POST201 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。

现在的问题是,如果我想使用生成器来设置返回代码,我必须在我的方法中返回一个对象。如何仍返回要序列化的对象?ResponseResponseUser

a) 在 servlet 响应上设置代码

解决这个问题的一种方法是获取一个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代码的令人不快的复苏,更糟糕的是,它会导致标头在此之后不可修改,因为它们已经在网络上发送了。

b) 将响应对象与实体一起使用

在这种情况下,最佳解决方案是使用 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();
}

在这种情况下,我们使用响应生成器类的 created 方法将状态代码设置为 201。我们通过 entity() 方法将实体对象(user)传递给响应。

结果是,HTTP 代码是 401,正如我们想要的那样,响应的正文与之前返回 User 对象时的 JSON 完全相同。它还添加了一个位置标头。

Response 类具有许多用于不同状态 (stati ?) 的生成器方法,例如:

Response.accepted() Response.ok() Response.noContent() Response.notAcceptable()

注意:hateoas 对象是我开发的一个帮助器类,用于帮助生成资源 URI。你需要在这里提出自己的机制;)

仅此而已。

我希望这个冗长的回应可以帮助某人:)