Netty 환경에서 http post 요청 보내기

아무튼 간에·2023년 1월 31일
0

Netty

목록 보기
2/4

처음엔 TCP Client를 사용하려고 하였으나 일방적으로 데이터를 보내기만 하면 되는 단방향 통신이었기 때문에 http 요청을 사용하기로 함

참고: HTTP 통신과 TCP 통신의 차이와 이해

이 글을 참고해서 작성했다. 사실 거의 똑같다. 참고글에서는 직접 json String으로 하드코딩했지만 나는 ObjectMapper로 jsonString을 만든 것 뿐...


전송 핸들러

@Component NettyHttpRequestHandler.java

@Component
public class NettyHttpRequestHandler {

    public void nettyHttpRequest(HostEntity requestEntity) {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new HttpClientCodec());
                pipeline.addLast(new NettyHttpResponseHandler());
            }
        });

        ObjectMapper mapper = new ObjectMapper();
        String objectToJsonString = null;
        try {
            objectToJsonString = mapper.writeValueAsString(requestEntity.getHostList());
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String content = "{\"host\":" + objectToJsonString + "}";	// body 데이터
        log.info("Request Body: " + content);
        try {
            URI uri = new URI("http://" + requestEntity.getIp() + ":" + requestEntity.getPort() + "/sync-host");
            FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri.getRawPath());
            request.headers().set(HttpHeaderNames.HOST, uri.getHost());
            request.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
            request.headers().set(HttpHeaderNames.ACCEPT, HttpHeaderValues.APPLICATION_JSON);

            ByteBuf byteBuf = Unpooled.copiedBuffer(content, StandardCharsets.UTF_8);
            request.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
            request.content().clear().writeBytes(byteBuf);

            Channel ch = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel();
            ch.writeAndFlush(request);
            ch.closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }
}
  • EventLoopGroup: EventLoop를 그룹핑한 인터페이스
  • NioEventLoopGroup: non-blocking I/O
    EventLoopGroup에서 하나의 Runnable task를 수행하면 EventLoop에 할당한 뒤에 수행을 맡김
  • Bootstrap: 네티로 작성한 네트워크 어플리케이션의 동작 방식과 환경을 설정하는 도우미 클래스. 아래 설정들이 가능함.
    - group(): 이벤트 루프 (단일 스레드, 다중 스레드)
    - channel(): 전송 계층 (소켓 모드 및 I/O 종류)
    - handler(): 채널 데이터 가공 핸들러
  • ChannelInitializer<SocketChannel>: 소켓 채널 인스턴스 생성
    - initChannel(): 소켓 채널이 생성될 때(서버와 연결을 맺을 때) 실행되는 메소드
    - ChannelPipeline(): 채널 파이프라인 설정
    - HttpClientCodec(): Http 코덱
    - NettyHttpResponseHandler(): 응답에 대한 처리 핸들러(직접 구현)
  • URI: URI 생성
  • FullHttpRequest: HttpRequestFullHttpMessage를 결합해 HTTP 요청함
  • DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, java.lang.String uri): http 버전, method, uri 정보를 넣어 요청 객체 생성 후 필요한 헤더 설정
    - CONNECTION: 연결 응답 완료 후 후 네트워크 연결을 열린 상태(keep-alive)로 유지할지 여부
  • ByteBuf: 자바의 ByteBuffer와 유사함. 단, 읽기 쓰기 전환 없이 사용 가능.
    - UnPooled: 풀링 되지 않는 ByteBuf 인스턴스를 생성하는 클래스
    - copiedBuffer() : 지정된 데이터를 복사 하는 ByteBuf 를 반환
  • Channel: 채널 연결 및 요청 보내기

응답 핸들러

NettyHttpResponseHandler.java

public class NettyHttpResponseHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpResponse) {
            HttpResponse response = (HttpResponse) msg;

            System.out.println("STATUS: " + response.status());
            System.out.println("VERSION: " + response.protocolVersion());
            System.out.println();

            if (!response.headers().isEmpty()) {
                for (CharSequence name: response.headers().names()) {
                    for (CharSequence value: response.headers().getAll(name)) {
                        System.out.println("HEADER: " + name + " = " + value);
                    }
                }
                System.out.println();
            }
            System.out.println("CONTENT [");
        }

        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;

            System.out.print(content.content().toString(CharsetUtil.UTF_8));
            System.out.flush();

            if (content instanceof LastHttpContent) {
                System.out.println();
                System.out.println("] END OF CONTENT");
                ctx.close();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
  • 응답 객체 호출 순서
    - DefaultHttpReponse: 응답 헤더 정보
    • DefaultHttpContent: 응답 body 데이터
      = EmptyLastHttpContent: 응답의 마지막을 알리는 플래그 값

응답 핸들러는 이해하기 어려운 부분은 없으므로 이정도로 마무리.
이렇게 만들어서 실제로 이렇게 적용했다.


참고

profile
armton garnet

0개의 댓글