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.
Изменения в API Jetty 12: как заменить HttpInput.Interceptor и HttpOutput.Interceptor?
В Jetty 11 и более ранних версиях я использовал кастомные реализации HttpInput.Interceptor и HttpOutput.Interceptor, переопределяя методы readFrom и write для добавления собственной логики.
При миграции на Jetty 12 эти API больше недоступны. Какой альтернативный подход или рекомендуемая замена существует в Jetty 12 для достижения аналогичного поведения? Как это реализовать в новой версии?
Пример кода из Jetty 9, 10, 11:
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 http больше не работают
- Рекомендуемая замена: Content.Source и Content.Sink в org eclipse jetty
- Миграция с jetty 11 на jetty 12: примеры кода
- Альтернативы для jetty server и jetty java: non-blocking API
- Проблемы race conditions и lifecycle контента в jetty servlet
- Дополнительные ресурсы: документация jetty 12 и GitHub
- Источники
- Заключение
Изменения в 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 и Content.Sink в org eclipse jetty
Основная замена — 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:
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)
Старый код:
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):
@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)
Аналогично:
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:
@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
- Официальная миграция 11→12: Шаги по шагам.
- Javadoc Content.Source: Полный API.
- GitHub: #9038, #7326, PR #9040.
Присоединяйтесь к сообществу — issues ожидают ваших вопросов.
Источники
- Jetty Issue #9038 — Обсуждение удаления HttpInput.Interceptor и замены на Content.Source: https://github.com/jetty/jetty.project/issues/9038
- Jetty Javadoc HttpInput — Документация deprecated интерфейсов и Content API в Jetty 12: https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/ee9/nested/HttpInput.html
- Jetty Migration Guide 11 to 12 — Руководство по миграции API Jetty HTTP с примерами asInputStream: https://jetty.org/docs/jetty/12/programming-guide/migration/11-to-12.html
- Jetty Issue #7326 — Анализ проблем контракта HttpInput.Interceptor в Jetty 11: https://github.com/jetty/jetty.project/issues/7326
- 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 взлетит. В итоге, изменения упрощают код, а не усложняют. Удачи с апгрейдом!
В 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.
Контракт 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.
PR #9040 для Jetty 12 удаляет HttpInput.Interceptor и HttpOutput.Interceptor, упрощая AsyncContentProducer в org.eclipse.jetty. Это исправляет #9038, устраняя проблемы API в Jetty 12. Автор: Simone Bordet, merged.
