JAX-RS-как вернуть код состояния JSON и HTTP вместе?



Я пишу веб-приложение REST (NetBeans 6.9, JAX-RS, TopLink Essentials) и пытаюсь вернуть JSON и код состояния HTTP. У меня есть готовый и рабочий код, который возвращает JSON при вызове метода HTTP GET из клиента. По существу:



@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 не должны использоваться для такой вещи?

765   12  

12 ответов:

вот пример:

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

посмотри ответ класса.

обратите внимание, что вы всегда должны указывать тип контента, особенно если вы передаете несколько типов контента, но если каждое сообщение будет представлено как JSON, вы можете просто аннотировать метод с помощью @Produces("application/json")

существует несколько вариантов использования для установки кодов состояния HTTP в веб-службе REST, и по крайней мере один из них не был достаточно документирован в существующих ответах (т. е. когда вы используете авто-магическую сериализацию JSON/XML с использованием JAXB, и вы хотите вернуть объект для сериализации, но также код состояния, отличный от значения по умолчанию 200).

Итак, позвольте мне попробовать и перечислить различные варианты использования и решения для каждого из них:

1. Код ошибки (500, 404,...)

наиболее распространенный вариант использования, когда вы хотите вернуть код состояния, отличный от 200 OK это когда происходит ошибка.

например:

  • объект запрашивается, но он не существует (404)
  • запрос семантически неверен (400)
  • пользователь не авторизован (401)
  • существует проблема с подключением к базе данных (500)
  • etc..

а) бросить исключение

в таком случае, я думаю, что самый чистый способ справиться с проблемой-это исключение. Это исключение будет обработано с помощью ExceptionMapper, что переведет исключение в ответ с соответствующим кодом ошибки.

вы можете использовать значение по умолчанию ExceptionMapper который поставляется с предварительно настроенным Джерси (и я думаю, что это то же самое с другими реализациями) и бросить любой из существующих подклассов javax.ws.rs.WebApplicationException. Это предопределенные типы исключений, которые предварительно сопоставляются с различными кодами ошибок, например:

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

Etc. Вы можете найти список здесь: API

кроме того, вы можете определить свои собственные исключения и ExceptionMapper классы, и добавить эти картографы в джерси с помощью@Provider аннотация (источник пример):

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 builder для построения ответа с предполагаемым кодом.

в этом случае возвращаемый тип метода должен быть 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 статус код.

один из подходов заключается в использовании реакции объекта, как описано выше, и установить тело запроса самостоятельно. Однако при этом вы теряете возможность использовать автоматическую сериализацию в XML или JSON, предоставляемую JAXB.

это исходный метод, возвращающий объект сущности, который будет сериализован в JSON с помощью 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;
}

это вернет представление JSON вновь созданного пользователя, но статус возврата будет 200, а не 201.

теперь проблема в том, если я хочу использовать Response builder чтобы установить код возврата, я должен вернуть Response объект в моем методе. Как я все еще возвращаю User объект для сериализации?

a) установите код на ответ сервлета

один из подходов к решению этой проблемы заключается в том, чтобы получить объект запроса сервлета и установить код ответа вручную, как показано в ответе Гарета Уилсона :

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

обратите внимание, что, чтобы заставить его работать, я должен был смыть ответ. Это неприятное возрождение низкоуровневого кода API сервлета в нашем хорошем ресурсе JAX_RS, и что еще хуже, это заставляет заголовки быть неизменяемыми после этого, потому что они уже были отправлены по проводу.

b) используйте объект ответа с сущностью

лучшим решением в этом случае является использование объекта ответа и установка сущность, которая будет сериализована на этом объекте ответа. Было бы неплохо сделать объект ответа универсальным, чтобы указать тип объекта полезной нагрузки в этом случае, но в настоящее время это не так.

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

в этом случае мы используем созданный метод класса построителя ответов, чтобы установить код состояния в 201. Мы передаем объект сущности (пользователь) в ответ с помощью метода entity ().

результатом является то, что HTTP-код 401, как мы хотели, и тело ответ точно такой же JSON, как и раньше, когда мы только что вернули объект User. Он также добавляет заголовок местоположения.

класс ответа имеет ряд методов компоновщика для разных статусов (stati ?) например :

ответ.общепринятый() Ответ.ладно() Ответ.noContent() Ответ.notAcceptable()

NB: объект hateoas-это вспомогательный класс, который я разработал для создания URI ресурсов. Вам нужно будет придумать свой собственный механизм здесь ;)

вот и все.

Я надеюсь, что этот длинный ответ поможет кому-то :)

ответ hisdrewness будет работать, но он изменяет весь подход к тому, чтобы позволить провайдеру, такому как Jackson+JAXB, автоматически конвертировать возвращаемый объект в некоторый выходной формат, такой как JSON. Вдохновленный Apache CXF post (который использует класс CXF-specific) я нашел один способ установить код ответа, который должен работать в любой реализации 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
}

если вы хотите, чтобы ваш слой ресурсов был чистым от 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;
  }
}

в случае, если вы хотите изменить код состояния из-за исключения, с 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();
    }

}

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 для которого запрашивается. (по крайней мере вот как это должно быть в системах покоя)

Если ваш 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, но у меня есть аналогичный сценарий, где я использую:

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

пожалуйста, посмотрите на пример здесь, он лучше всего иллюстрирует проблему и как она решается в последней (2.3.1) версии Джерси.

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

Это в основном включает в себя определение пользовательского исключения и сохранение возвращаемого типа в качестве сущности. При возникновении ошибки выдается исключение, в противном случае вы возвращаете POJO.

Я нашел очень полезным построить также сообщение 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();
    }
}

кроме того, обратите внимание, что по умолчанию Джерси будет переопределять тело ответа в случае http-кода 400 или более.

чтобы получить указанный объект в качестве тела ответа, попробуйте добавить следующий init-param к вашему Джерси в вашем интернете.файл конфигурации XML :

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

Я использую Джерси 2.0 с читателями и писателями тела сообщения. У меня был свой тип возврата метода как определенная сущность, которая также использовалась в реализации средства записи тела сообщения, и я возвращал тот же pojo, SkuListDTO. @ПОЛУЧИТЬ @Consumes ({"application / xml", " application/json"}) @Производит ({"application / xml", " application / json"}) @Path ("/skuResync")

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

все, что я изменил, это то, что я оставил реализацию писателя в покое, и она все еще работал.

public Response getSkuData()
...
return Response.status(Response.Status.FORBIDDEN).entity(dfCoreResultListDTO).build();

Comments

    Ничего не найдено.