Я заметил, что новые экземпляры обработчиков не создаются для каждого http-запроса. У меня есть несколько переменных, которые определены на уровне экземпляра. Эти значения устанавливаются на основе запроса. при внимательном рассмотрении я обнаружил, что эти значения не установлены заново, а имеют значение из первого запроса.
Вот мой код обработчика
@Component
@ChannelHandler.Sharable
public class CustomHandler extends ChannelDuplexHandler {
private final StringBuilder buf = new StringBuilder();
private final String foo;
private final String val;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//parse the request and set the variables
if (foo!=null) {
foo = request.getUri()
}
if (val!=null) {
val = getQueryParamsOf("key");
}
buf.append(val);
}
}
Буфер не очищается. Для каждого нового запроса я все еще вижу старый буфер.
если я сделаю запрос /foobar?key=netty
Я вижу баф = нетти в первом вызове.
последующие вызовы, буф = неттинетти и buf=nettynettynetty и так далее.
Кроме того, переменные foo
и val
никогда не становятся нулевыми после первого запроса.
Насколько я понимаю, для каждого запроса будет создан новый обработчик. Но так как я использовал @ChannelHander.Sharable
, могут быть повторно использованы те же обработчики
поэтому я закомментировал @ChannelHander.Sharable
, первый запрос проходит нормально. При следующем запросе я получаю следующую ошибку.
io.netty.channel.ChannelPipelineException: my.example.handlers.CustomHandler is not a @Sharable handler, so can't be added or removed multiple times.
at io.netty.channel.DefaultChannelPipeline.checkMultiplicity(DefaultChannelPipeline.java:625)
at io.netty.channel.DefaultChannelPipeline.addLast(DefaultChannelPipeline.java:208)
at io.netty.channel.DefaultChannelPipeline.addLast(DefaultChannelPipeline.java:409)
at io.netty.channel.DefaultChannelPipeline.addLast(DefaultChannelPipeline.java:396)
at my.example.CustomInitializer.initChannel(CustomInitializer.java:35)
at my.example.CustomInitializer.initChannel(CustomInitializer.java:16)
at io.netty.channel.ChannelInitializer.initChannel(ChannelInitializer.java:113)
at io.netty.channel.ChannelInitializer.handlerAdded(ChannelInitializer.java:105)
at io.netty.channel.DefaultChannelPipeline.callHandlerAdded0(DefaultChannelPipeline.java:637)
Вот мой код инициализатора
Пользовательский инициализатор
public class CustomIniatializer extends ChannelInitializer<SocketChannel> {
@Autowired
private ChannelDuplexHandler customHandler;
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new HttpServerCodec());
p.addLast(new HttpObjectAggregator(8*1024, true));
p.addLast(customHandler);
}
}
Одна из вещей, которую вы должны помнить при использовании ChannelInitializer
, это тот факт, что метод initChannel
вызывается для каждого нового открываемого соединения.
Все, что требует уникального состояния, должно быть создано внутри этого метода.
Глядя на ваш код, я вижу, что вы правильно создаете новые экземпляры LoggingHandler
, HttpServerCodec
и HttpObjectAggregator
, но ссылаетесь на «общий» экземпляр вашего класса customHandler
.
Хотя вы могли бы решить свою проблему, просто используя new CustomHandler ()
внутри метода initChannel
, на самом деле вы демонстрируете разные намерения, используя систему автоподключения пружин.
Есть 2 других решения, которые мы можем использовать:
Вместо прямого автоматического подключения экземпляра ChannelDuplexHandler
вам нужно подключить фабрику, которая производит экземпляры этого класса:
public interface ChannelDuplexHandlerFactory {
public ChannelDuplexHandler getChannelDuplexHandler();
}
@Component
public class ChannelDuplexHandlerFactoryImplementation implements ChannelDuplexHandlerFactory {
public ChannelDuplexHandler getChannelDuplexHandler() {
return new CustomHandler();
}
}
public class CustomIniatializer extends ChannelInitializer<SocketChannel> {
@Autowired
private ChannelDuplexHandler customHandler;
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new HttpServerCodec());
p.addLast(new HttpObjectAggregator(8*1024, true));
p.addLast(customHandler.getChannelDuplexHandler());
}
}
Еще одно решение, которое вы могли бы использовать, — это переменные, хранящиеся внутри текущего канала, это более продвинутая техника, которая может быть полезна в определенных ситуациях:
@Component
@ChannelHandler.Sharable
public class CustomHandler extends ChannelDuplexHandler {
private static final AttributeKey<StringBuilder> BUF_KEY = AttributeKey.newInstance("BUF_KEY");
private static final AttributeKey<String> FOO_KEY = AttributeKey.newInstance("FOO_KEY");
private static final AttributeKey<String> VAL_KEY = AttributeKey.newInstance("VAL_KEY");
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Channel ch = ctx.channel();
final StringBuilder buf = ch.attr(BUF_KEY).get();
String foo = ch.attr(FOO_KEY).get();
String val = ch.attr(VAL_KEY).get();
// Parse the request and set the variables
if (foo != null) {
foo = request.getUri()
}
if (val != null) {
val = getQueryParamsOf("key");
}
buf.append(val);
ch.attr(FOO_KEY).set(foo);
ch.attr(VAL_KEY).set(val);
}
}
У меня такая же проблема даже с новой реализацией Handler()
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
.....
p.addLast("httprequest", new HttpServerHandler(config));
}
}
HttpServerHander
private String uuid;
public HttpServerHandler(final StaticConfig staticConfig) {
this.uuid = UUID.randomUUID().toString();
}
Этот uuid не уникален для каждого вызова.
Я удалил пару переменных (buf и foo) из экземпляра в область уровня метода. Но я не мог избежать val. поэтому я использовал поле AttributeKey на основе поля, поскольку в моем случае это кажется более элегантным.