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

问题

我正在编写REST Web应用程序(NetBeans 6.9,JAX-RS,TopLink Essentials)并尝试返回JSONHTTP状态代码。我已经准备好代码并且在从客户端调用HTTP GET方法时返回JSON。主要有:

@Path("get/id")
@GET
@Produces("application/json")
public M_機械 getMachineToUpdate(@PathParam("id") String id) {

    // some code to return JSON ...

    return myJson;
}

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

我试过用HttpServletResponse

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

但这使浏览器认为它是"真正的"500,因此输出网页是一个常规的HTTP 500错误页面。

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


#1 热门回答(300 赞)

这是一个例子:

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

看看Responseclass。

请注意,你应始终指定内容类型,尤其是在传递多种内容类型的情况下,但如果每条消息都将表示为JSON,则可以使用@Produces("application/json")注释该方法


#2 热门回答(156 赞)

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

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

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

发生错误时,如果要返回不同于200 OK的状态代码,则是最常见的用例。

例如:

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

a)抛出异常

在这种情况下,我认为处理问题最简洁的方法是抛出异常。此异常将由aExceptionMapper处理,它将异常转换为具有相应错误代码的响应。

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

  • BadRequestException(400)
  • InternalServerErrorException(500)
  • NotFoundException(404)

等等。你可以在此处找到列表:API

或者,你可以定义自己的自定义异常和ExceptionMapper类,并通过@Providerannotation(source of this example)的平均值将这些映射器添加到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)使用"响应"构建器

设置状态代码的另一种方法是使用aResponsebuilder来构建具有预期代码的响应。

在这种情况下,方法的返回类型必须为javax.ws.rs.core.Response。这在其他各种反应中有所描述,例如hisdrewness'接受的答案,看起来像这样:

@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的成功代码,以及你在正文中返回的内容。

一个常见的用例是当你创建一个新实体(POSTrequest)并希望返回有关此新实体或实体本身的信息以及a201 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。

现在问题是如果我想使用Responsebuilder来设置返回码,我必须在我的方法中返回一个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构建器类的created方法将状态代码设置为201.我们通过entity()方法将实体对象(user)传递给响应。

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

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

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

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

就是这样。

我希望这个冗长的回应有助于某人:)


#3 热门回答(65 赞)

通过hedrewness得到的答案将起作用,但它修改了让Jackson JAXB之类的提供程序自动将返回的对象转换为某种输出格式(如JSON)的整个方法。受Apache CXFpost(使用特定于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
}

改进:在找到另一个相关的answer之后,我了解到可以将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
}