JAX-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类。

请注意,您应该始终指定一个内容types,尤其是在您传递多个内容types的情况下,但是如果每条消息都将被表示为JSON,则只需使用@Produces("application/json")对该方法进行注释

有几种用于在REST Web服务中设置HTTP状态代码的用例,并且至less有一个在现有答案中没有足够的文档logging(即,当您使用JAXB使用自动奇妙的JSON / XML序列化,并且您想返回要被序列化的对象,还有一个不同于缺省值200的状态码)。

因此,让我尝试一下列举不同的用例和解决scheme:

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

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

例如:

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

a)抛出exception

在这种情况下,我认为处理这个问题最简洁的方法就是抛出exception。 这个exception将由一个ExceptionMapper来处理,它将把exception转换成具有相应错误代码的响应。

你可以使用预先configuration了Jersey的默认的ExceptionMapper (我想它和其他实现一样)并抛出javax.ws.rs.WebApplicationException任何现有的子类。 这些是预先定义的exceptiontypes,它们被预映射到不同的错误代码,例如:

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

等等你可以在这里find列表: API

或者,您可以定义自己的自定义exception和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(); } } 

注意:您也可以为您使用的现有exceptiontypes编写ExceptionMappers。

b)使用Response构build器

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

在这种情况下,你的方法的返回types必须是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的成功代码,以及你在主体中返回的内容。

一个经常使用的例子是当你创build一个新的实体( POST请求)并且想要返回关于这个新实体的信息或者实体本身,以及一个201 Created状态码。

一种方法是像上面描述的那样使用响应对象,并自己设置请求的主体。 但是,通过这样做,您将无法使用由JAXB提供的XML或JSON的自动序列化function。

这是返回一个实体对象的原始方法,它将被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; } 

这将返回新创build的用户的JSON表示,但返回状态将是200,而不是201。

现在的问题是,如果我想使用Response构build器来设置返回码,我必须在我的方法中返回一个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)与实体一起使用响应对象

在这种情况下,最好的解决scheme是使用Response对象,并将实体设置为在此响应对象上进行序列化。 在这种情况下,使Response对象通用来指示有效负载实体的types将是很好的,但不是当前的情况。

 @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类创build的方法将状态码设置为201.我们通过entity()方法将实体对象(用户)传递给响应。

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

Response类有许多不同的状态(stati?)的构build方法,如:

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

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

就是这样。

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

退出的答案将会起作用,但它会修改让Jackson + JAXB等提供程序自动将返回的对象转换为某种输出格式(如JSON)的整个方法。 受到Apache CFX 文章 (使用CFX特定的类)的启发,我find了一种方法来设置应该在任何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 } 

改进:find另一个相关的答案之后 ,我了解到,可以注入HttpServletResponse作为成员variables,即使对于单例服务类(至less在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对象,那么我build议你使用@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(); } 

这是filter的肉:

 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支持标准/自定义HTTP代码。 请参阅ResponseBuilder和ResponseStatus,例如:

http://jackson.codehaus.org/javadoc/jax-rs/1.0/javax/ws/rs/core/Response.ResponseBuilder.html#status%28javax.ws.rs.core.Response.Status%29

请记住,JSON信息更多的是与资源/应用程序相关的数据。 HTTP代码更多地涉及所请求的CRUD操作的状态。 (至less在REST-ful系统中应该是这样的)

如果您的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; } 

如果你想修改状态码,那么使用JAX-RS 2.0,你可以像这样实现一个ExceptionMapper。 这为整个应用程序处理这种exception。

 @Provider public class UnauthorizedExceptionMapper implements ExceptionMapper<EJBAccessException> { @Override public Response toResponse(EJBAccessException exception) { return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build(); } } 

请看这里的例子,它最好地说明了这个问题,以及如何在最新(2.3.1)版本的Jersey中解决这个问题。

https://jersey.java.net/documentation/latest/representations.html#d0e3586

它基本上涉及定义一个自定义exception,并保持返回types为实体。 出现错误时抛出exception,否则返回POJO。

我发现build立一个带有重复代码的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,但我有一个类似的场景,我使用:

 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); 

另外,请注意,默认情况下,如果http代码为400或更多,Jersey将覆盖响应主体。

为了让指定的实体作为响应主体,请尝试在web.xmlconfiguration文件中将以下init-param添加到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> 

我正在使用jersey2.0与消息正文阅读器和作家。 我有我的方法返回types作为一个具体的实体,也用于消息正文编写器的实现中,我正在返回相同的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();