Netty, как проверить обработчик, который использует удаленный адрес клиента - PullRequest
0 голосов
/ 01 августа 2020

У меня есть TCP-сервер Netty с Spring Boot 2.3.1 со следующим обработчиком:

@Slf4j
@Component
@RequiredArgsConstructor
@ChannelHandler.Sharable
public class QrReaderProcessingHandler extends ChannelInboundHandlerAdapter {

    private final CarParkPermissionService permissionService;
    private final Gson gson = new Gson();

    private String remoteAddress;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.fireChannelActive();

        remoteAddress = ctx.channel().remoteAddress().toString();
        if (log.isDebugEnabled()) {
            log.debug(remoteAddress);
        }
        ctx.writeAndFlush("Your remote address is " + remoteAddress + ".\r\n");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info("CLIENT_IP: {}", remoteAddress);

        String stringMsg = (String) msg;
        log.info("CLIENT_REQUEST: {}", stringMsg);

        String lowerCaseMsg = stringMsg.toLowerCase();

        if (RequestType.HEARTBEAT.containsName(lowerCaseMsg)) {
            HeartbeatRequest heartbeatRequest = gson.fromJson(stringMsg, HeartbeatRequest.class);
            log.debug("heartbeat request: {}", heartbeatRequest);

            HeartbeatResponse response = HeartbeatResponse.builder()
                .responseCode("ok")
                .build();
            ctx.writeAndFlush(response + "\n\r");
        }
    }

DTO запроса:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HeartbeatRequest {
    private String messageID;
}

DTO ответа:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HeartbeatResponse {
    private String responseCode;
}

Логи c довольно просто. Только мне нужно знать IP-адрес клиента.

Мне тоже нужно его протестировать.

Я искал много ресурсов для тестирования обработчиков для Netty, например

Однако это не сработало для me.

Для EmbeddedChannel у меня следующая ошибка - Your remote address is embedded.

Вот код:

@ActiveProfiles("test")
@RunWith(MockitoJUnitRunner.class)
public class ProcessingHandlerTest_Embedded {

    @Mock
    private PermissionService permissionService;
    private EmbeddedChannel embeddedChannel;
    private final Gson gson = new Gson();

    private ProcessingHandler processingHandler;


    @Before
    public void setUp() {
        processingHandler = new ProcessingHandler(permissionService);
        embeddedChannel = new EmbeddedChannel(processingHandler);
    }

    @Test
    public void testHeartbeatMessage() {
        // given
        HeartbeatRequest heartbeatMessage = HeartbeatRequest.builder()
                .messageID("heartbeat")
                .build();

        HeartbeatResponse response = HeartbeatResponse.builder()
                .responseCode("ok")
                .build();
        String request = gson.toJson(heartbeatMessage).concat("\r\n");
        String expected = gson.toJson(response).concat("\r\n");

        // when
        embeddedChannel.writeInbound(request);

        // then
        Queue<Object> outboundMessages = embeddedChannel.outboundMessages();
        assertEquals(expected, outboundMessages.poll());
    }
}

Вывод:

22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_IP: embedded
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {"messageID":"heartbeat"}

22:21:29.067 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)

org.junit.ComparisonFailure: 
<Click to see difference>

введите описание изображения здесь

Однако я не знаю, как провести точное тестирование для такого случая.

Вот фрагмент из конфигурации:

@Bean
@SneakyThrows
public InetSocketAddress tcpSocketAddress() {
    // for now, hostname is: localhost/127.0.0.1:9090
    return new InetSocketAddress("localhost", nettyProperties.getTcpPort());

    // for real client devices: A05264/172.28.1.162:9090
    // return new InetSocketAddress(InetAddress.getLocalHost(), nettyProperties.getTcpPort());
}

@Component
@RequiredArgsConstructor
public class QrReaderChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final StringEncoder stringEncoder = new StringEncoder();
    private final StringDecoder stringDecoder = new StringDecoder();

    private final QrReaderProcessingHandler readerServerHandler;
    private final NettyProperties nettyProperties;

    @Override
    protected void initChannel(SocketChannel socketChannel) {
        ChannelPipeline pipeline = socketChannel.pipeline();

        // Add the text line codec combination first
        pipeline.addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Delimiters.lineDelimiter()));

        pipeline.addLast(new ReadTimeoutHandler(nettyProperties.getClientTimeout()));
        pipeline.addLast(stringDecoder);
        pipeline.addLast(stringEncoder);
        pipeline.addLast(readerServerHandler);
    }
}

Как проверить обработчик с IP-адресом клиента?

1 Ответ

1 голос
/ 15 августа 2020

Две вещи, которые могут помочь:

  1. Не добавляйте аннотацию с помощью @ChannelHandler.Sharable, если ваш обработчик НЕ является общедоступным. Это может ввести в заблуждение. Удалите ненужное состояние из обработчиков. В вашем случае вы должны удалить переменную-член remoteAddress и убедиться, что Gson и CarParkPermissionService могут быть повторно использованы и являются поточно-ориентированными.

  2. "Your remote address is embedded" НЕ является ошибкой . На самом деле это сообщение, написанное вашим обработчиком на исходящий канал (см. Ваш channelActive() метод)

Так что похоже, что это может сработать.

EDIT

После ваших комментариев, вот некоторые пояснения по второму пункту. Я имею в виду, что:

  • ваш код, использующий EmbeddedChannel, почти правильный. Есть просто недопонимание ожидаемых результатов (assert).

Чтобы сделать модульный тест успешным, вам нужно либо:

  • , чтобы прокомментировать эту строку в channelActive(): ctx.writeAndFlush("Your remote ...")
  • или для опроса второго сообщения от Queue<Object> outboundMessages в testHeartbeatMessage()

Действительно, когда вы это сделаете:

// when
embeddedChannel.writeInbound(request);

(1) Фактически вы открываете канал один раз, что вызывает событие channelActive(). У вас нет журнала, но мы видим, что переменная remoteAddress впоследствии не имеет значения null, что означает, что она была назначена в методе channelActive().

(2) В конце метода channelActive() вы в конечном итоге уже отправляете сообщение, записывая в конвейере канала, как показано в этой строке:

ctx.writeAndFlush("Your remote address is " + remoteAddress + ".\r\n");
// In fact, this is the message you see in your failed assertion.

(3) Затем сообщение, написанное embeddedChannel.writeInbound(request), получено и может быть прочитано, что вызывает событие channelRead(). На этот раз мы видим это в вашем журнале:

22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_IP: embedded
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {"messageID":"heartbeat"}
22:21:29.067 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)

(4) В конце channelRead(ChannelHandlerContext ctx, Object msg) вы отправите сообщение секунда (ожидаемый):

HeartbeatResponse response = HeartbeatResponse.builder()
     .responseCode("ok")
     .build();
ctx.writeAndFlush(response + "\n\r");

Следовательно, с помощью следующего кода вашего модульного теста ...

Queue<Object> outboundMessages = embeddedChannel.outboundMessages();
assertEquals(expected, outboundMessages.poll());

... вы сможете poll() два сообщения :

  • "Your remote address is embedded"
  • "{ResponseCode":"ok"}

Имеет ли смысл для вас?

...