Как заставить Джерси использовать сжатие GZip для тела сообщения ответа

Я пытаюсь написать простое приложение Джерси, которое отправляет файлы с Джерси-клиентом на сервер Джерси и обратно. Однако файлы, по-видимому, кодируются на пути от клиента к серверу, но не наоборот. Интересно, как я могу изменить это поведение.

Я тестирую это в простом примере:

public class GZipEncodingTest extends JerseyTest {

  private static final String PATH = "/";
  private static final String QUESTION = "foo", ANSWER = "bar";
  private static final String ENCODING_GZIP = "gzip";

  @Path(PATH)
  public static class MyResource {
    @POST
    public Response handle(String question) throws IOException {
      assertEquals(QUESTION, question);
      return Response.ok(ANSWER).build(); // (1)
    }
  }

  @Override
  protected Application configure() {
    enable(TestProperties.LOG_TRAFFIC);
    enable(TestProperties.DUMP_ENTITY);
    return new ResourceConfig(MyResource.class, GZipEncoder.class);
  }

  @Override
  @SuppressWarnings("unchecked")
  protected void configureClient(ClientConfig config) {
    config.register(new EncodingFeature(ENCODING_GZIP, GZipEncoder.class));
  }

  @Test
  public void testHeaders() throws Exception {
    Response response = target().path(PATH).request().post(Entity.text(QUESTION));
    assertEquals(ANSWER, response.readEntity(String.class));
  }
}

Из зарегистрированного дампа я могу сказать, что запрос имеет значение: кодировка содержимого сигнализируется в заголовке и применяется к телу сообщения запроса. Также установлено значение Accept-Encoding. Сервер понимает применяемое сжатие gzip и распаковывает тело сообщения запроса. Тем не менее, он игнорирует тот факт, что клиент принимает ответ gzipped и отправляет тело сообщения ответа несжатым.

Когда я добавляю encoding(ENCODING_GZIP) в строке (1) в цепочке Response -builder, я получаю результат, который я ищу. Однако я хочу применить кодировку только в том случае, если она была помечена как приемлемая в запросе. Кроме того, я хочу объединить это приложение, а не только для конкретных ответов.

Я могу, конечно, добавить такую ​​функцию вручную с помощью WriterInterceptor:

public class GZipWriterInterceptor implements WriterInterceptor {
  @Override
  public void aroundWriteTo(WriterInterceptorContext context) 
      throws IOException, WebApplicationException {
    context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, ENCODING_GZIP);
    context.proceed();
  }
}

но я убежден, что это ненужная плита котла.

EncodingFeature кажется только частью клиентской библиотеки. Я в основном ищут возможность заставить сервер Джерси кодировать данные как gzip всякий раз, когда запрос предлагал кодирование посредством accept-encoding.

Когда я пытаюсь искать решения в Интернете, я нахожу много. Большинство из них относятся к Джерси 1. Некоторые из них предлагают добавить слушателя к GrizzlyServer (который был бы Джерси, а не JAX-RS?). Затем в дереве зависимостей Jersey 2 есть много классов, которые предлагают кодировку GZip:

  • org.glassfish.grizzly.http.GZipContentEncoding
  • org.glassfish.jersey.message.GZipEncoder
  • org.glassfish.grizzly.compression.zip.GZipEncoder
  • org.glassfish.grizzly.compression.zip.GZipDecoder
  • org.glassfish.grizzly.compression.zip.GZipFilter

Я обнаружил, что люди в Интернете предлагают использовать любой из них, хотя мне нравится думать, что org.glassfish.jersey кажется правильным выбором, поскольку он является фактической зависимостью от Джерси. Не говорить о тех, которые находятся в связанных библиотеках ApacheConnector. Я понятия не имею, какой из них я должен использовать.

Ответ 1

Я понял это, просмотрев библиотеку Джерси. Для серверной части необходима следующая конфигурация:

@Override
@SuppressWarnings("unchecked")
protected Application configure() {
    ResourceConfig resourceConfig = new ResourceConfig(MyResource.class);
    EncodingFilter.enableFor(resourceConfig, GZipEncoder.class);
    return resourceConfig;
}

В диалоговом режиме EncodingFilter#enableFor(ResourceConfig.Class<? extends ContentEncoder>[]) регистрирует EncodingFilter и указанный GZipEncoder с данным ResourceConfig.

Я предполагаю, что причина этого объезда в регистрации заключается в том, что любая кодировка должна произойти в два этапа. Во-первых, EncodingFilter (который является фактическим ContainerResponseFilter изменяет заголовок ответа, устанавливая Content-Encoding в gzip. В то же время фильтр не может изменять поток сущности тела сообщения, так как фильтр вызывается, прежде чем этот поток будет даже создан. Поэтому модификация потока должна обрабатываться WriterInterceptor, которая запускается после обработки фильтра, а также после создания потока сущности.

По этой причине только регистрация GZipEncoder будет работать для декодирования запроса, когда клиентский столбец Content-Encoding установлен на gzip, который происходит независимо от создания сервера.

Пример, который я дал с моим "GZipWriterInterceptor", в основном, является плохо реализованной версией EncodingFilter. Конечно, заголовок должен быть установлен в фильтре, а не в перехватчике. Он говорит в документации:

В то время как фильтры предназначены в первую очередь для манипулирования запросами и параметры ответа, такие как HTTP-заголовки, URI и/или HTTP-методы, перехватчики предназначены для манипулирования сущностями посредством манипулирования поток ввода/вывода объекта

Следовательно, кодирование gzip нельзя просто активировать, зарегистрировав GZipEncoder, его также необходимо зарегистрировать в фильтре. Вот почему я ожидал, что оба будут включены в Feature.

Важно. В Джерси есть два класса EncodingFilter. Один принадлежит клиенту, другой принадлежит серверной реализации. Не используйте неправильный, поскольку они делают принципиально разные вещи. К сожалению, вы будете иметь их обоих в своем пути к классу при выполнении модульных тестов, поскольку они полагаются на клиентский интерфейс.