처음엔 TCP Client를 사용하려고 하였으나 일방적으로 데이터를 보내기만 하면 되는 단방향 통신이었기 때문에 http 요청을 사용하기로 함
이 글을 참고해서 작성했다. 사실 거의 똑같다. 참고글에서는 직접 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/OEventLoopGroup
에서 하나의 Runnable task를 수행하면 EventLoop에 할당한 뒤에 수행을 맡김Bootstrap
: 네티로 작성한 네트워크 어플리케이션의 동작 방식과 환경을 설정하는 도우미 클래스. 아래 설정들이 가능함.group()
: 이벤트 루프 (단일 스레드, 다중 스레드)channel()
: 전송 계층 (소켓 모드 및 I/O 종류)handler()
: 채널 데이터 가공 핸들러ChannelInitializer<SocketChannel>
: 소켓 채널 인스턴스 생성initChannel()
: 소켓 채널이 생성될 때(서버와 연결을 맺을 때) 실행되는 메소드ChannelPipeline()
: 채널 파이프라인 설정HttpClientCodec()
: Http 코덱NettyHttpResponseHandler()
: 응답에 대한 처리 핸들러(직접 구현)URI
: URI 생성FullHttpRequest
: HttpRequest
와 FullHttpMessage
를 결합해 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
: 응답의 마지막을 알리는 플래그 값응답 핸들러는 이해하기 어려운 부분은 없으므로 이정도로 마무리.
이렇게 만들어서 실제로 이렇게 적용했다.