RS - 如何将JSON和HTTP状态码一起返回?

我正在编写一个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状态码不能用于这样的事情?


这是一个例子:

@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的状态码)。

因此,让我尝试并列举每种不同的使用案例和解决方案:

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

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

例如:

  • 一个实体被请求但它不存在(404)
  • 请求语义不正确(400)
  • 该用户未被授权(401)
  • 数据库连接有问题(500)
  • 等等..
  • a)抛出异常

    在那种情况下,我认为处理这个问题最简洁的方法是抛出一个异常。 这个异常将由一个ExceptionMapper处理,它会将异常转换为具有适当错误代码的响应。

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

  • BadRequestException(400)
  • InternalServerErrorException(500)
  • NotFoundException(404)
  • 等你可以在这里找到列表: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。

    b)使用Response构建器

    另一种设置状态码的方法是使用“ 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();
        }
        ...
    }
    

    2.成功,但不是200

    另一种想要设置返回状态的情况是操作成功时,但您希望返回不同于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对象?

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

    在这种情况下,我们使用Response builder类创建的方法将状态码设置为201.我们通过entity()方法将实体对象(用户)传递给响应。

    其结果是HTTP代码是我们想要的,并且响应的主体与我们刚刚返回User对象时的JSON完全相同。 它还添加了一个位置标题。

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

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

    NB:仇恨对象是我开发的一个帮助类,用于帮助生成资源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
    }
    
    链接地址: http://www.djcxy.com/p/1227.html

    上一篇: RS — How to return JSON and HTTP status code together?

    下一篇: What is a correct mime type for docx, pptx etc?