netty 간단한 http client, application/json 요청하기
buycycle를 활용하면 증권사 api를 http post 방식으로 요청 및 응답을 받을 수 있다.
buycycle.name
'Buycycle'은 증권사 API를 HTTP Json으로 요청 및 응답 받을 수 있습니다. 요청 받은 Json 메시지를 증권사 API 양식에 맞게 변환해 주는 자바 기반의 오픈 소스 입니다. HTTP RESTful을 제공함으로써 사용자
buycycle.name
네티를 활용하여 buycycle에 http post application/json 요청하는 예제이다. 간단한 http 요청이기에 비교적 소스는 간결하다. channelpipeline 에 등록된 핸들러는 Http 코덱을 위한 HttpClientCodec과 응답에 대한 처리를 구현한 ResponseHandler 두 종류이다. 보통 코덱은 인코더와 디코더로 구분하여 등록하나 HttpClientCodec은 CombinedChannelDuplexHandler 상속 받은 핸들러이기에 인코더와 디코더가 함께 있다.
package test;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
public class NettyHttpRequest {
public static void main(String[] args){
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());
}
});
String content =
"{" +
" \"body\": {" +
" \"trName\": \"t1511\"," +
" \"bNext\": false," +
" \"query\": {" +
" \"upcode\": \"001\"" +
" }" +
" }," +
" \"header\": {" +
" \"uuid\": \"7b81c375-d9b9-43c1-8449-77e561a979f2\"" +
" }" +
"}";
try {
URI uri = new URI("http://localhost:7771/data/ebest/query");
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);
ByteBuf byteBuf = Unpooled.copiedBuffer(content, StandardCharsets.UTF_8);
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
request.content().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();
}
}
}
DefaultFullHttpRequest 객체 생성 시 Http 버전 정보와, method, raw path 파라미터를 함께 넣을 수 있다. http body에 해당하는 content 파라미터도 넣을 수 있으나 해당 예제에선 request.content().writeBytes() 메소드를 이용하였다.
Http header 정보로 Host 값과 content type, connection 정보도 같이 넣어주었다. chunked 전송이 아니므로 content_length 정보도 꼭 넣어주어야 한다. chunked 전송시 io.netty.handler.codec.http.HttpUtil.setTransferEncodingChunked(); 이용시 setTransferEncodingChunked 메소드의 소스를 보면 content_length header 정보는 삭제하는 걸 알 수 있다.
다음은 인바운드에 대한 handler 구현체이다.
package test;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
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();
}
}
해당 핸들러를 디버깅 해보면 channelRead0 메소드가 3번 호출 되는걸 알 수 있다. 처음으로 DefaultHttpReponse instance 가 호출 되고 그 다음에 DefaultHttpContent 그 다음으로 EmptyLastHttpContent이다. EmptyLastHttpContent 객체는 실제로 데이터는 없고 마지막을 알리는 플래그 값이라고 이해하면 쉽다. DefaultHttpReponse에서는 헤더에 대한 정보를 출력하고 DefaultHttpContent에서는 Body 부분에 담긴 데이터를 출력 한다.