프로그래밍/buycycle

netty 간단한 http client, application/json 요청하기

말춘이 2021. 4. 8. 22:40
반응형

  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 부분에 담긴 데이터를 출력 한다.

반응형