Установите заголовки ответов JAX-RS в реализацию, не подвергая HttpServletResponse в интерфейсе

У меня есть реализация сервера RESTful, а также библиотека для клиентов для выполнения вызовов, все с использованием JAX-RS. Компоненты сервера разделены на интерфейс FooResource и реализацию FooResourceService.

Для того, чтобы библиотеки клиентов и серверов могли совместно использовать путь RESTful и другие определения, я хотел разделить интерфейс FooResource в своем собственном проекте:

@Path(value = "foo")
public interface FooResource {

  @GET
  public Bar getBar(@PathParam(value = "{id}") int id) {

Я хочу установить некоторые заголовки в ответе. Один простой способ сделать это - использовать @Context HttpServletResponse в сигнатуре метода:

  public Bar getBar(@PathParam(value = "{id}") int id, @Context HttpServletResponse servletResponse) {

Но проблема в том, что это предоставляет детали реализации в интерфейсе. Более конкретно, внезапно требуется, чтобы мой проект определения REST (который делится между клиентской и серверной библиотекой), чтобы задействовать зависимость javax.servlet-api - то, что клиент не нуждается (или желает).

Как моя реализация RESTful-ресурса может устанавливать заголовки HTTP-ответов, не втягивая эту зависимость в интерфейс ресурсов?

Я видел одно сообщение, рекомендующее вводить HttpServletResponse как член класса. Но как это будет работать, если моя реализация службы ресурсов является одноэлементной? Использует ли он какой-то прокси-сервер с локалями потоков или что-то, что определяет правильный ответ сервлета, хотя одноэлементный класс используется одновременно несколькими потоками? Есть ли другие решения?

Ответ 1

Правильный ответ, по-видимому, заключается в том, чтобы ввести HttpServletResponse в переменную-член реализации, как я уже отмечал в другом посте.

@Context  //injected response proxy supporting multiple threads
private HttpServletResponse servletResponse;

Несмотря на то, что peeskillet указал, что в полуофициальном списке для Джерси HttpServletResponse не HttpServletResponse качестве одного из типов прокси-серверов, при трассировке кода по крайней мере RESTEasy, похоже, создает прокси (org.jbos[email protected]xxxxxxxx). Так что, насколько я могу судить, кажется, что потокобезопасное внедрение одноэлементной переменной-члена происходит.

См. Также fooobar.com/questions/34126/....

Ответ 2

Таким образом, инъекция HttpServletResponse кажется пустой. Только отдельные типы, способные прокси-сервера, могут вводить инъекции в одиночные. Я считаю, что полный список выглядит следующим образом:

HttpHeaders, Request, UriInfo, SecurityContext

Это несколько указано в спецификации JAX-RS, но более подробно объясняется в справочном руководстве Джерси

Исключение существует для конкретных объектов запроса, которые могут быть введены даже в поля конструктора или класса. Для этих объектов среда выполнения будет вводить прокси-серверы, которые могут одновременно запрашивать больше запросов. Эти объекты запроса HttpHeaders, Request, UriInfo, SecurityContext. Эти прокси могут быть введены с помощью аннотации @Context.

SecurityContext может быть специфичным для Джерси, поскольку он не указан в спецификации, но я не уверен.

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

Одна идея состоит в том, чтобы использовать javax.ws.rs.container.ContainerResponseFilter вместе с HttpHeaders для установки временного заголовка запроса. Вы можете получить доступ к этому заголовку через ContainerRequestContext, переданный методу filter. Затем просто установите заголовок ответа через ContainerResponseContext, также переданный методу filter. Если заголовок не является специфическим для контекста этого метода ресурсов, то это еще проще. Просто установите заголовок в фильтре.

Но пусть говорят, что заголовок зависит от выполнения метода ресурса. Тогда вы можете сделать что-то вроде

@Singleton
@Path("/singleton")
public class SingletonResource {

    @Context
    javax.ws.rs.core.HttpHeaders headers;

    @GET
    public String getHello() {

        String result = resultFromSomeCondition(new Object());
        headers.getRequestHeaders().putSingle("X-HELLO", result);
        return "Hello World";
    }

    private String resultFromSomeCondition(Object condition) {
        return "World";
    }
}

Тогда ContainerResponseFilter может выглядеть примерно так:

@Provider
public class SingletonContainerResponseFilter 
                            implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext crc, 
            ContainerResponseContext crc1) throws IOException {
        String header = crc.getHeaderString("X-HELLO");
        crc1.getHeaders().putSingle("X-HELLO", "World");
    } 
}

И только потому, что через этот фильтр запускаются только одноэлементные классы, мы можем просто использовать аннотацию @NameBinding

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;

@NameBinding
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SingletonHeader {}

...

@SingletonHeader
public class SingletonResource {

...

@SingletonHeader
public class SingletonContainerResponseFilter 
                        implements ContainerResponseFilter {

Это единственный способ, с помощью которого я могу справиться с этой ситуацией.


Ресурсы

Ответ 3

@Path("/foo")
public interface FooResource {

    @GET
    @Path("{id}")
    public Response getBar(@PathParam("id") int id) {
        Bar bar = new Bar();
        //Do some logic on bar
        return Response.ok().entity(bar).header("header-name", "header-value").build()
    }
}

Возвращает JSON-представление экземпляра bar с кодом состояния 200 и заголовком header-name со значением header-value. Он должен выглядеть примерно так:

{
    "bar-field": "bar-field-value",
    "bar-field-2": "bar-field-2"
}