JAX-RS —如何同时返回JSON和HTTP状态码?

发布于 2024-10-11 15:25:40 字数 680 浏览 5 评论 0原文

我正在编写一个 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;
}

但我还想随 JSON 数据一起返回 HTTP 状态代码(500、200、204 等)。

我尝试使用 HttpServletResponse

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

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

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

I'm writing a REST web app (NetBeans 6.9, JAX-RS, TopLink Essentials) and trying to return JSON and HTTP status code. I have code ready and working that returns JSON when the HTTP GET method is called from the client. Essentially:

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

    // some code to return JSON ...

    return myJson;
}

But I also want to return an HTTP status code (500, 200, 204, etc.) along with the JSON data.

I tried to use HttpServletResponse:

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

But this made the browser think it's a "real" 500 so the output web page was a regular HTTP 500 error page.

I want to return an HTTP status code so that my client-side JavaScript can handle some logic depending on it (to e.g. display the error code and message on an HTML page). Is this possible or should HTTP status codes not be used for such thing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

清风不识月 2024-10-18 15:25:40

这是一个示例:

@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< /a> 类。

请注意,您应该始终指定内容类型,尤其是在传递多个内容类型时,但如果每条消息都表示为 JSON,则只需使用 @Produces("application/json")

Here's an example:

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

Take a look at the Response class.

Note that you should always specify a content type, especially if you are passing multiple content types, but if every message will be represented as JSON, you can just annotate the method with @Produces("application/json")

彼岸花似海 2024-10-18 15:25:40

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

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

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

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

例如:

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

请求了一个 异常

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

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

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

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

或者,您可以定义自己的自定义异常和 ExceptionMapper< /code> 类,并通过 @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 :

    @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) 使用响应构建器

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

在这种情况下,您的方法的返回类型必须是 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 请求)并希望返回有关该新实体或实体本身的信息以及 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 对象通用以指示有效负载实体的类型,但目前情况并非如此。

@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() 方法将实体对象(用户)传递给响应。

结果是 HTTP 代码是我们想要的 401,响应正文与我们之前返回 User 对象时的 JSON 完全相同。它还添加了位置标头。

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

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

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

仅此而已。

我希望这个冗长的回复对某人有所帮助:)

There are several use cases for setting HTTP status codes in a REST web service, and at least one was not sufficiently documented in the existing answers (i.e. when you are using auto-magical JSON/XML serialization using JAXB, and you want to return an object to be serialized, but also a status code different than the default 200).

So let me try and enumerate the different use cases and the solutions for each one:

1. Error code (500, 404,...)

The most common use case when you want to return a status code different than 200 OK is when an error occurs.

For example:

  • an entity is requested but it doesn't exist (404)
  • the request is semantically incorrect (400)
  • the user is not authorized (401)
  • there is a problem with the database connection (500)
  • etc..

a) Throw an exception

In that case, I think that the cleanest way to handle the problem is to throw an exception. This exception will be handled by an ExceptionMapper, that will translate the exception into a response with the appropriate error code.

You can use the default ExceptionMapper that comes pre-configured with Jersey (and I guess it's the same with other implementations) and throw any of the existing sub-classes of javax.ws.rs.WebApplicationException. These are pre-defined exception types that are pre-mapped to different error codes, for example:

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

Etc. You can find the list here: API

Alternatively, you can define your own custom exceptions and ExceptionMapper classes, and add these mappers to Jersey by the mean of the @Provider annotation (source of this example):

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 :

    @Provider
    public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException> 
    {
        @Override
        public Response toResponse(MyApplicationException exception) 
        {
            return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();  
        }
    }

Note: you can also write ExceptionMappers for existing exception types that you use.

b) Use the Response builder

Another way to set a status code is to use a Response builder to build a response with the intended code.

In that case, your method's return type must be javax.ws.rs.core.Response. This is described in various other responses such as hisdrewness' accepted answer and looks like this :

@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. Success, but not 200

Another case when you want to set the return status is when the operation was successful, but you want to return a success code different than 200, along with the content that you return in the body.

A frequent use case is when you create a new entity (POST request) and want to return info about this new entity or maybe the entity itself, together with a 201 Created status code.

One approach is to use the response object just like described above and set the body of the request yourself. However, by doing this you loose the ability to use the automatic serialization to XML or JSON provided by JAXB.

This is the original method returning an entity object that will be serialized to JSON by JAXB:

@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user){
    User newuser = ... do something like DB insert ...
    return newuser;
}

This will return a JSON representation of the newly created user, but the return status will be 200, not 201.

Now the problem is if I want to use the Response builder to set the return code, I have to return a Response object in my method. How do I still return the User object to be serialized?

a) Set the code on the servlet response

One approach to solve this is to obtain a servlet request object and set the response code manually ourselves, like demonstrated in Garett Wilson's answer :

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

The method still returns an entity object and the status code will be 201.

Note that to make it work, I had to flush the response. This is an unpleasant resurgence of low-level Servlet API code in our nice JAX_RS resource, and much worse, it causes the headers to be unmodifiable after this because they were already sent on the wire.

b) Use the response object with the entity

The best solution, in that case, is to use the Response object and set the entity to be serialized on this response object. It would be nice to make the Response object generic to indicate the type of the payload entity in that case, but is not the currently the case.

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

In that case, we use the created method of the Response builder class in order to set the status code to 201. We pass the entity object (user) to the response via the entity() method.

The result is that the HTTP code is 401 as we wanted, and the body of the response is the exact same JSON as we had before when we just returned the User object. It also adds a location header.

The Response class has a number of builder method for different statuses (stati ?) such as :

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

NB: the hateoas object is a helper class that I developed to help generate resources URIs. You will need to come up with your own mechanism here ;)

That's about it.

I hope this lengthy response helps somebody :)

豆芽 2024-10-18 15:25:40

hisdrewness 的答案是可行的,但它修改了整个方法,让 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
}

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

The answer by hisdrewness will work, but it modifies the whole approach to letting a provider such as Jackson+JAXB automatically convert your returned object to some output format such as JSON. Inspired by an Apache CXF post (which uses a CXF-specific class) I've found one way to set the response code that should work in any JAX-RS implementation: inject an HttpServletResponse context and manually set the response code. For example, here is how to set the response code to CREATED when appropriate.

@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
}

Improvement: After finding another related answer, I learned that one can inject the HttpServletResponse as a member variable, even for singleton service class (at least in RESTEasy)!! This is a much better approach than polluting the API with implementation details. It would look like this:

@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
}
你没皮卡萌 2024-10-18 15:25:40

如果您希望资源层中没有 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;
  }
}

If you like to keep your resource layer clean of Response objects, then I recommend you use @NameBinding and binding to implementations of ContainerResponseFilter.

Here's the meat of the annotation:

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

Here's the meat of the 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;
        }
      }
    }
  }
}

And then the implementation on your resource simply becomes:

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;
  }
}
咋地 2024-10-18 15:25:40

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

I found it very useful to build also a json message with repeated code, like this:

@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();
    }
}
风追烟花雨 2024-10-18 15:25:40

如果您因为异常而想要更改状态代码,则可以使用 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();
    }

}

In case you want to change the status code because of an exception, with JAX-RS 2.0 you can implement an ExceptionMapper like this. This handles this kind of exception for the whole app.

@Provider
public class UnauthorizedExceptionMapper implements ExceptionMapper<EJBAccessException> {

    @Override
    public Response toResponse(EJBAccessException exception) {
        return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build();
    }

}
挽你眉间 2024-10-18 15:25:40

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

If your WS-RS needs raise an error why not just use the 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;
}
陌若浮生 2024-10-18 15:25:40

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 系统中应该是这样)

JAX-RS has support for standard/custom HTTP codes. See ResponseBuilder and ResponseStatus, for example:

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

Keep in mind that JSON information is more about the data associated with the resource/application. The HTTP codes are more about the status of the CRUD operation being requested. (at least that is how it's supposed to be in REST-ful systems)

听不够的曲调 2024-10-18 15:25:40

请查看此处的示例,它最好地说明了该问题以及在最新(2.3.1)版本的 Jersey 中如何解决该问题。

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

它基本上涉及定义自定义异常并将返回类型保留为实体。当出现错误时,抛出异常,否则,返回 POJO。

Please look at the example here, it best illustrates the problem and how it is solved in the latest (2.3.1) version of Jersey.

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

It basically involves defining a custom Exception and keeping the return type as the entity. When there is an error, the exception is thrown, otherwise, you return the POJO.

白云悠悠 2024-10-18 15:25:40

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

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

I'm not using JAX-RS, but I've got a similar scenario where I use:

response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
茶色山野 2024-10-18 15:25:40

另请注意,默认情况下,如果 http 代码为 400 或以上,Jersey 将覆盖响应正文。

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

Also, notice that by default Jersey will override the response body in case of an http code 400 or more.

In order to get your specified entity as the response body, try to add the following init-param to your Jersey in your web.xml configuration file :

    <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>
凑诗 2024-10-18 15:25:40

以下代码对我有用。通过带注释的 setter 注入 messageContext 并在我的“add”方法中设置状态代码。

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.apache.cxf.jaxrs.ext.MessageContext;

public class FlightReservationService {

    MessageContext messageContext;

    private final Map<Long, FlightReservation> flightReservations = new HashMap<>();

    @Context
    public void setMessageContext(MessageContext messageContext) {
        this.messageContext = messageContext;
    }

    @Override
    public Collection<FlightReservation> list() {
        return flightReservations.values();
    }

    @Path("/{id}")
    @Produces("application/json")
    @GET
    public FlightReservation get(Long id) {
        return flightReservations.get(id);
    }

    @Path("/")
    @Consumes("application/json")
    @Produces("application/json")
    @POST
    public void add(FlightReservation booking) {
        messageContext.getHttpServletResponse().setStatus(Response.Status.CREATED.getStatusCode());
        flightReservations.put(booking.getId(), booking);
    }

    @Path("/")
    @Consumes("application/json")
    @PUT
    public void update(FlightReservation booking) {
        flightReservations.remove(booking.getId());
        flightReservations.put(booking.getId(), booking);
    }

    @Path("/{id}")
    @DELETE
    public void remove(Long id) {
        flightReservations.remove(id);
    }
}

The following code worked for me. Injecting the messageContext via annotated setter and setting the status code in my "add" method.

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.apache.cxf.jaxrs.ext.MessageContext;

public class FlightReservationService {

    MessageContext messageContext;

    private final Map<Long, FlightReservation> flightReservations = new HashMap<>();

    @Context
    public void setMessageContext(MessageContext messageContext) {
        this.messageContext = messageContext;
    }

    @Override
    public Collection<FlightReservation> list() {
        return flightReservations.values();
    }

    @Path("/{id}")
    @Produces("application/json")
    @GET
    public FlightReservation get(Long id) {
        return flightReservations.get(id);
    }

    @Path("/")
    @Consumes("application/json")
    @Produces("application/json")
    @POST
    public void add(FlightReservation booking) {
        messageContext.getHttpServletResponse().setStatus(Response.Status.CREATED.getStatusCode());
        flightReservations.put(booking.getId(), booking);
    }

    @Path("/")
    @Consumes("application/json")
    @PUT
    public void update(FlightReservation booking) {
        flightReservations.remove(booking.getId());
        flightReservations.put(booking.getId(), booking);
    }

    @Path("/{id}")
    @DELETE
    public void remove(Long id) {
        flightReservations.remove(id);
    }
}
策马西风 2024-10-18 15:25:40

扩展答案Nthalk Microprofile OpenAPI 您可以使用 < 来将返回代码与您的文档对齐a href="https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponse.java" rel= “nofollow noreferrer”>@APIResponse 注释。

这允许标记 JAX-RS 方法,例如

@GET
@APIResponse(responseCode = "204")
public Resource getResource(ResourceRequest request) 

您可以使用 ContainerResponseFilter

@Provider
public class StatusFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
        if (responseContext.getStatus() == 200) {
            for (final var annotation : responseContext.getEntityAnnotations()) {
                if (annotation instanceof APIResponse response) {
                    final var rawCode = response.responseCode();
                    final var statusCode = Integer.parseInt(rawCode);

                    responseContext.setStatus(statusCode);
                }
            }
        }
    }

}

当您在方法上添加多个注释时,会出现警告,例如

@APIResponse(responseCode = "201", description = "first use case")
@APIResponse(responseCode = "204", description = "because you can")
public Resource getResource(ResourceRequest request) 

Expanding on the answer of Nthalk with Microprofile OpenAPI you can align the return code with your documentation using @APIResponse annotation.

This allows tagging a JAX-RS method like

@GET
@APIResponse(responseCode = "204")
public Resource getResource(ResourceRequest request) 

You can parse this standardized annotation with a ContainerResponseFilter

@Provider
public class StatusFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
        if (responseContext.getStatus() == 200) {
            for (final var annotation : responseContext.getEntityAnnotations()) {
                if (annotation instanceof APIResponse response) {
                    final var rawCode = response.responseCode();
                    final var statusCode = Integer.parseInt(rawCode);

                    responseContext.setStatus(statusCode);
                }
            }
        }
    }

}

A caveat occurs when you put multiple annotations on your method like

@APIResponse(responseCode = "201", description = "first use case")
@APIResponse(responseCode = "204", description = "because you can")
public Resource getResource(ResourceRequest request) 
时光匆匆的小流年 2024-10-18 15:25:40

我正在将 jersey 2.0 与消息正文读取器和写入器一起使用。我将我的方法返回类型作为特定实体,该实体也用于消息正文编写器的实现,并且我返回相同的 pojo,即 SkuListDTO。
@得到
@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();

I'm using jersey 2.0 with message body readers and writers. I had my method return type as a specific entity which was also used in the implementation of the message body writer and i was returning the same pojo, a SkuListDTO.
@GET
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
@Path("/skuResync")

public SkuResultListDTO getSkuData()
    ....
return SkuResultListDTO;

all i changed was this, I left the writer implementation alone and it still worked.

public Response getSkuData()
...
return Response.status(Response.Status.FORBIDDEN).entity(dfCoreResultListDTO).build();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文