Программирование

Jetty 12: замена HttpInput.Interceptor и HttpOutput.Interceptor

Изменения в API Eclipse Jetty 12: удалены HttpInput.Interceptor и HttpOutput.Interceptor. Рекомендуемая замена — Content.Source и Content.Sink для jetty http. Примеры миграции с Jetty 11, non-blocking код и решение race conditions.

6 ответов 1 просмотр

Изменения в API Jetty 12: как заменить HttpInput.Interceptor и HttpOutput.Interceptor?

В Jetty 11 и более ранних версиях я использовал кастомные реализации HttpInput.Interceptor и HttpOutput.Interceptor, переопределяя методы readFrom и write для добавления собственной логики.

При миграции на Jetty 12 эти API больше недоступны. Какой альтернативный подход или рекомендуемая замена существует в Jetty 12 для достижения аналогичного поведения? Как это реализовать в новой версии?

Пример кода из Jetty 9, 10, 11:

java
import org.eclipse.jetty.server.HttpInput;
import org.eclipse.jetty.server.Request;

import java.io.IOException;
import java.nio.ByteBuffer;

public class JettyInputInterceptor implements HttpInput.Interceptor {

 private final Request baseRequest;

 JettyInputInterceptor(Request baseRequest) {
 this.baseRequest = baseRequest;
 }

 @Override
 public HttpInput.Content readFrom(HttpInput.Content content) {
 
 }
}

В Eclipse Jetty 12 интерфейсы HttpInput.Interceptor и HttpOutput.Interceptor полностью удалены из API Jetty HTTP, чтобы упростить управление контентом и избежать race conditions. Рекомендуемая замена — использование Content.Source для входного потока и Content.Sink для выходного в org.eclipse.jetty, с методами вроде asInputStream() и asOutputStream() для плавной миграции с Jetty 11. Это позволяет перехватывать и модифицировать данные без старых хаков, сохраняя non-blocking природу сервера.


Содержание


Изменения в eclipse jetty 12: удаление HttpInput.Interceptor и HttpOutput.Interceptor

Jetty 12 — это не просто апгрейд, а серьёзный рефакторинг под Jakarta EE 10. Старые интерфейсы HttpInput.Interceptor и HttpOutput.Interceptor, которые вы использовали для перехвата readFrom и write, исчезли. Почему? Разработчики Eclipse Jetty решили, что они создавали больше проблем, чем решали.

В документации Jetty 12 эти интерфейсы помечены как deprecated ещё в переходной версии, а в финальной EE10-реализации их просто выкинули. Методы вроде addInterceptor в HttpInput остались, но они бесполезны — без интерфейса их не на что цеплять. Аналогично с HttpOutput.Interceptor: его логика теперь встроена в Content.Sink.

Если ваш код из Jetty 9/10/11 выглядел как в примере вопроса, компиляция сломается сразу. Но хорошие новости: миграция проще, чем кажется. Переходим к сути.


Почему старые интерцепторы в jetty http больше не работают

Представьте: вы перехватываете чанк данных в readFrom, но забываете его полностью прочитать. Что дальше? В Jetty 11 это приводило к утечкам памяти и race conditions — контент “висел” в пуле, пока GC не вмешается. Контракт был размытым: возвращай null, специальный контент или что-то своё? Хаос.

Симоне Бордет (sbordet), один из ключевых разработчиков Jetty, в issue #9038 объясняет: HttpInput.Interceptor похож на ContentSourceTransformer, но без строгого lifecycle. А HttpOutput.Interceptor дублировал Content.Sink.write(). Результат — PR #9040, где всё удалили.

В issue #7326 Лудовик Орбан (lorban) добавляет: старый API не гарантировал полное потребление данных, что ломало async-цепочки в Jetty HTTP. В версии 12 это упростили ради надёжности. Ваш интерцептор работал? Круто, но в продакшене он мог подвести под нагрузкой.

А теперь вопрос: стоит ли жалеть? Нет. Новое API чище и быстрее.


Основная замена — Content.Source для input и Content.Sink для output. Они живут в org.eclipse.jetty.http3 или базовом org.eclipse.jetty.server, в зависимости от протокола.

  • Content.Source: Производит контент запроса. Методы: nextContentChunk(), read(boolean), isError().
  • Content.Sink: Потребляет ответ. Методы: write(ByteBuffer, boolean last), completed().

Для вашего случая: в сервлете берите Content.Source.from(request) и оборачивайте логику. Официальное руководство по миграции советует request.getContentSource().asInputStream() — это мостик к blocking-коду.

Пример базовой замены input:

java
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.http.Content;

Content.Source source = request.getContentSource();
InputStream intercepted = source.asInputStream(); // Ваш readFrom здесь

Output проще: response.asSink().write(buffer, lastContent, callback). Это non-blocking по умолчанию, но совместимо с потоком.

Звучит абстрактно? В следующих разделах разберём код шаг за шагом.


Миграция с jetty 11 на jetty 12: примеры кода

Давайте возьмём ваш пример из Jetty 11 и мигрируем. Цель: логировать или модифицировать чанки.

Для Input (readFrom)

Старый код:

java
public class JettyInputInterceptor implements HttpInput.Interceptor {
 @Override
 public HttpInput.Content readFrom(HttpInput.Content content) {
 // Логика: логируем, модифицируем
 System.out.println("Read: " + content.getByteBuffer());
 return content;
 }
}

Новый в Jetty 12 (servlet doPost/doGet):

java
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
 Request baseRequest = (Request) req;
 Content.Source source = baseRequest.getContentSource();
 
 // Перехват: используем Transformer или кастомный wrapper
 InputStream interceptedStream = new InterceptedInputStream(source.asInputStream());
 byte[] data = interceptedStream.readAllBytes(); // Или non-blocking read
 
 // Обработка...
}

class InterceptedInputStream extends FilterInputStream {
 public InterceptedInputStream(InputStream in) { super(in); }
 
 @Override
 public int read() throws IOException {
 int b = super.read();
 if (b != -1) System.out.println("Read byte: " + b); // Ваша логика
 return b;
 }
}

Для Output (write)

Аналогично:

java
Content.Sink sink = ((Response) resp).asSink();
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
sink.write(buffer, false, Callback.NOOP); // last=false для чанков
sink.write(lastBuffer, true, Callback.NOOP); // Завершить
sink.completed();

Если blocking: OutputStream out = sink.asOutputStream();. Минимальные изменения — и готово.

Но подождите, это blocking-вариант. А если нужен чистый async?


Альтернативы для jetty server и jetty java: non-blocking API

Jetty server заточен под non-blocking, так зачем тормозить потоком? Используйте Content.Source напрямую.

Пример async input в Handler:

java
@Override
public boolean handle(Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
 Content.Source source = request.getContentSource();
 source.read(new Callback() {
 @Override
 public void succeeded() {
 Content.Chunk chunk = source.nextContentChunk();
 if (chunk != null) {
 ByteBuffer bb = chunk.getByteBuffer();
 // Модифицируйте bb, залогируйте
 source.read(this); // Рекурсия для следующего чанка
 }
 }
 });
 return false; // Async
}

Для output — цепочка Sink. В Jetty Java это идеально для высоконагруженных сервисов вроде Spring Jetty.

Преимущества? Нет блокировок, меньше GC, масштабируемость. Минус — чуть больше кода. Стоит ли? Если трафик >1000 req/s — да.


Проблемы race conditions и lifecycle контента в jetty servlet

Старые интерцепторы игнорировали lifecycle: чанк прочитан, но не released? Boom — утечка. В Jetty 12 Content.Chunk требует явного succeeded() или failed().

Типичные ловушки:

  • Не вызвать source.completed() после EOF.
  • Параллельные reads в multi-threaded сервлете.
  • ErrorContent: проверяйте chunk.isError().

Решение: всегда используйте Callback с source.demand(1) для pull-модели. В issue #9038 это ключевой момент — новый API вынуждает делать правильно.

Тестируйте под нагрузкой: JMeter + Jetty 12 покажет, если где-то race.


Дополнительные ресурсы: документация jetty 12 и GitHub

Присоединяйтесь к сообществу — issues ожидают ваших вопросов.


Источники

  1. Jetty Issue #9038 — Обсуждение удаления HttpInput.Interceptor и замены на Content.Source: https://github.com/jetty/jetty.project/issues/9038
  2. Jetty Javadoc HttpInput — Документация deprecated интерфейсов и Content API в Jetty 12: https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/ee9/nested/HttpInput.html
  3. Jetty Migration Guide 11 to 12 — Руководство по миграции API Jetty HTTP с примерами asInputStream: https://jetty.org/docs/jetty/12/programming-guide/migration/11-to-12.html
  4. Jetty Issue #7326 — Анализ проблем контракта HttpInput.Interceptor в Jetty 11: https://github.com/jetty/jetty.project/issues/7326
  5. Jetty PR #9040 — Pull request по удалению интерцепторов и упрощению lifecycle: https://github.com/jetty/jetty.project/pull/9040

Заключение

Миграция на Jetty 12 избавляет от хрупких интерцепторов, предлагая мощное Content.Source/Sink для Jetty HTTP. Начните с asInputStream() для быстрого порта, перейдите на non-blocking для производительности. Тестируйте lifecycle — и ваш Jetty server взлетит. В итоге, изменения упрощают код, а не усложняют. Удачи с апгрейдом!

Simone Bordet / Ведущий архитектор Webtide

В Jetty 12 интерфейс HttpInput.Interceptor имеет сигнатуру, аналогичную ContentSourceTransformer в Eclipse Jetty, рекомендуется выровнять контракт для избежания race conditions при освобождении чанков. HttpOutput.Interceptor должен стать подинтерфейсом Content.Sink из-за идентичной сигнатуры write(). Текущая реализация HttpInput.Interceptor приводит к проблемам с жизненным циклом контента в Jetty HTTP. Issue закрыта PR #9090 и #9040.

В Jetty 12 интерфейс HttpInput.Interceptor помечен как deprecated и удалён без замены в EE10-реализации org.eclipse.jetty. Методы addInterceptor, setInterceptor и getInterceptor остались в классе HttpInput, но бесполезны без интерфейса. HttpInput производит контент через ContentProducer.nextContent() с типами EofContent, ErrorContent и WrappingContent для Jetty server.

Ludovic Orban / Разработчик Webtide

Контракт HttpInput.Interceptor в Jetty 11 был расплывчатым, что привело к сложностям в управлении жизненным циклом контента, включая частичное чтение и возврат null или специального контента. В Jetty 12 требуется переработка для упрощения в Jetty Java, без обязательства полностью потреблять данные. Issue закрыта для версии 12.0.x.

При миграции с Jetty 11 на Jetty 12 используйте Content.Source.asInputStream(request) для InputStream и Content.Sink.asOutputStream(response) для OutputStream в Eclipse Jetty server, минимизируя изменения кода. Response в Jetty 12 реализует Content.Sink для non-blocking записи. Предпочтите Content API вместо blocking потоков в Jetty HTTP.

Simone Bordet / Ведущий архитектор Webtide

PR #9040 для Jetty 12 удаляет HttpInput.Interceptor и HttpOutput.Interceptor, упрощая AsyncContentProducer в org.eclipse.jetty. Это исправляет #9038, устраняя проблемы API в Jetty 12. Автор: Simone Bordet, merged.

Авторы
Simone Bordet / Ведущий архитектор Webtide
Ведущий архитектор Webtide
Ludovic Orban / Разработчик Webtide
Разработчик Webtide
Источники
GitHub / Платформа для хостинга кода
Платформа для хостинга кода
Портал документации API
Документация сервера приложений
Проверено модерацией
Модерация
Jetty 12: замена HttpInput.Interceptor и HttpOutput.Interceptor