首页 文章

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

提问于
浏览
214

我正在编写REST Web应用程序(NetBeans 6.9,JAX-RS,TopLink Essentials)并尝试返回JSON and 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状态代码不能用于此类事情?

12 回答

  • 6

    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操作的状态 . (至少这是它在REST-ful系统中应该如何)

  • 308

    这是一个例子:

    @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") 注释该方法即可 .

  • 4

    在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 注释(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)使用“响应”构建器

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

    在这种情况下,方法的返回类型必须是 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的成功代码,以及您在正文中返回的内容 .

    一个常见的用例是当您创建一个新实体( POST request)并希望返回有关此新实体或实体本身的信息以及 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构建器类的created方法将状态代码设置为201.我们通过entity()方法将实体对象(user)传递给响应 .

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

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

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

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

    就是这样 .

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

  • 4

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

    如果您希望保持资源层清除 Response 对象,那么我建议您使用 @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();
    }
    

    这是过滤器的主要内容:

    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;
      }
    }
    
  • 1

    如果您想因异常而更改状态代码,使用JAX-RS 2.0可以实现这样的ExceptionMapper . 这会处理整个应用程序的这种异常 .

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

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

    我没有使用JAX-RS,但我有一个类似的场景我使用:

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

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

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

    它主要涉及定义自定义Exception并将返回类型保持为实体 . 出现错误时,抛出异常,否则返回POJO .

  • 4

    我发现使用重复代码构建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();
        }
    }
    
  • 163

    此外,请注意,默认情况下,如果http代码为400或更高,Jersey将覆盖响应正文 .

    为了将您指定的实体作为响应主体,请尝试在web.xml配置文件中将以下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>
    
  • -1

    我正在使用带有消息体阅读器和作者的jersey 2.0 . 我有我的方法返回类型作为一个特定的实体,也用于消息体编写器的实现,我返回相同的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();
    

相关问题