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,例如:
请记住,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();