Установите заголовки ответов 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"
}