Third Party API 를 사용하기 위해서는 때론 Server to Server 통신을 해야할 때도 있다. (Client to Server 통신은 보안 상의 이유로 많은 제약이 있기 때문에…)

httpClient 는 Spring(Java) 에서 기본적으로 제공하는 패키지로 Request 정보를 원하는 URI 에 송신하고, 통신이 성공했을 시 Response 를 응답받으며, 이를 파싱하여 통신을 완료할 수 있다.


4가지의 HTTP Method

HTTP 1.1 부터 기본적인 HTTP Method 인 GET, POST 이외에도, OPTION METHOD 인 PUT, DELETE 등의 추가 메소드가 지원되고 있다. HTTP 1.1 RESTful한 API 통신을 위한 서버를 개발한다고 하면 위의 4가지(GET, POST, PUT, DELETE) 통신에 대한 응답 모듈을 개발하여야 한다.


각 HTTP Method 별 Request 객체 생성

하기 메소드들은 각 HTTP Method 별로 통신을 하기 위한 진입 메소드이다. (HTTP Method 별로 메소드를 구분해 놓은 이유는 아래에 적도록 하겠다)

/**
 * <pre>
 * HTTP Method(Get) 로 Request 진행
 * </pre>
 * @methodName    : executeHttpGet
 */
private Map<String, String> executeHttpGet(String uri) throws Exception{
 
    // 메소드 변수 선언
    HttpGet httpRequest = new HttpGet();
 
    // HTTP Request 공통 모듈 실행
    Map<String, String> map = new HashMap<String, String>();
    map = executeHttpClient(httpRequest, uri, map);
 
    return map;
}
 
/**
 * <pre>
 * HTTP Method(DELETE) 로 Request 진행
 * </pre>
 * @methodName    : executeHttpDelete
 */
private Map<String, String> executeHttpDelete(String uri) throws Exception{
 
    // 메소드 변수 선언
    HttpDelete httpRequest = new HttpDelete();
 
    // HTTP Request 공통 모듈 실행
    Map<String, String> map = new HashMap<String, String>();
    map = executeHttpClient(httpRequest, uri, map);
 
    return map;
}
 
/**
 * <pre>
 * HTTP Method(POST) 로 Request 진행
 * </pre>
 * @methodName    : executeHttpPost
 */
private Map<String, String> executeHttpPost(String uri, String body) throws Exception{
 
    // 메소드 변수 선언
    HttpPost httpRequest = new HttpPost();
 
    // 바디 설정
    if(body != null) {
        HttpEntity entity = new ByteArrayEntity(body.getBytes("UTF-8"));
        httpRequest.setEntity(entity);
    }
 
    // HTTP Request 공통 모듈 실행
    Map<String, String> map = new HashMap<String, String>();
    map = executeHttpClient(httpRequest, uri, map);
 
    return map;
}
 
/**
 * <pre>
 * HTTP Method(PUT) 로 Request 진행
 * </pre>
 * @methodName    : executeHttpPut
 */
private Map<String, String> executeHttpPut(String uri, String body) throws Exception{
 
    // 메소드 변수 선언
    HttpPut httpRequest = new HttpPut();
 
    // 바디 설정
    if(body != null) {
        HttpEntity entity = new ByteArrayEntity(body.getBytes("UTF-8"));
        httpRequest.setEntity(entity);
    }
 
    // HTTP Request 공통 모듈 실행
    Map<String, String> map = new HashMap<String, String>();
    map = executeHttpClient(httpRequest, uri, map);
 
    return map;
}

상기 두 개의 메소드(GET, DELETE) 와 그 아래의 메소드(POST, PUT) 의 차이점은 무엇일까? 그것은 매개 변수로 body 를 받으며, Request 객체 내에 해당 body 를 삽입할 수 있다는 차이점이다.

GET과 DELETE 는 통신에 body 가 필요하지 않으므로, 객체 내에 해당 기능이 구현되어 있지 않지만, POST과 PUT 은 setEntity(String body); 라는 기능이 구현되어 있다.


통신 및 Response 파싱

Request 객체가 생성되었다면, 적합한 URI 에 해당 정보로 통신을 취하고 응답받은 Response 객체를 파싱하여, 원하는 결과를 얻는 일만 남았다. 하기 메소드에 그에 대한 내용이 구현되어 있다.

/**
 * <pre>
 * HTTP Client Request 공통 실행 함수
 * </pre>
 * @methodName    : executeHttpClient
 */
private Map<String, String> executeHttpClient(HttpRequestBase httpRequest, String uri, Map<String, String> map){
 
    // 클라이언트 변수 선언
    CloseableHttpClient httpClient = HttpClients.createDefault();
 
    // 대상 URI 맵핑
    httpRequest.setURI(URI.create(uri));
 
    
    /*
    통신에 계정 권한이 필요한 경우, 헤더에 Base64 인코딩된 계정정보를 보내는 방식인 BaseAuth 방식을 사용할 수 있다.
    (그외의 방법으로는 OAuth 2.0 등의 방식이 있으며, 이는 따로 검색을 해보길 바란다.
    // Base64 인코딩
    String basicAuth = Base64.encodeBase64String(("YOUR-ACCOUNT-ID" + ":" + "YOUR-ACCOUNT-PW").getBytes());
    */
    
 
    // 헤더 설정
    httpRequest.setHeader("Authorization", "Basic " + basicAuth);
    httpRequest.setHeader("Content-Type", "application/json");
 
    CloseableHttpResponse httpResponse = null;
    try {
 
        // HTTP Request 실행
        httpResponse = httpClient.execute(httpRequest);
 
        // statusCode
        int statusCode = httpResponse.getStatusLine().getStatusCode();
        map.put("statusCode", String.valueOf(statusCode));
 
        // Response 결과 분기
        if((statusCode / 100) == 2) {
            map.put("result", "OK");
        }
        else {
            map.put("result", "ERROR");
        }
 
    }
    catch(Exception e) {
        e.printStackTrace();
    }
    finally {
        // Response 및 Client 객체 세션 종료(중요!)
        closeInstant(httpResponse);
        closeInstant(httpClient);
    }
 
    return map;
}

상기 메소드 중에서, 특히 눈여겨 봐야할 것은 finally 구문이다. 상기 소스코드는 어느 한줄이 빠져도 통신이 제대로 되지 않기 때문에, 디버깅이 쉬우나, finally 구문의 session close 를 하지 않는다면, 세션이 종료되지 않고 지속적으로 쌓여, 서버 과부하를 초래할 수 있다. (Open 한 Session 에 대해서는 반드시 Close 할 수 있도록 한다)


메소드 호출(통신 실행 예제)

public @ResponseBody Map<String, String> updateComment(@RequestParam Map<String, Object> param, ModelMap model, HttpServletRequest request, HttpServletResponse response) throws Exception{
 
    // ------- 파라미터 및 쿠키 정보 획득
    // AD 계정 로드
    HashMap<String, String> ldapMap = getMyLdapInfo(request, response);
    String ldapId = StringUtils.defaultIfEmpty(ldapMap.get("ldapId"), "");
    String ldapPw = StringUtils.defaultIfEmpty(ldapMap.get("ldapPw"), "");
 
    // 파라미터 획득 및 파싱
    String key = StringUtils.defaultIfEmpty(param.get("key").toString(), ""); // 이슈 키
    String id = StringUtils.defaultIfEmpty(param.get("id").toString(), ""); // 댓글 ID
    String comment = StringUtils.defaultIfEmpty(param.get("comment").toString(), ""); // 수정될 댓글 본문
 
    comment = comment.replaceAll("\r", "<br/>");
    comment = comment.replaceAll("\n", "<br/>");
 
    // BODY(JSON) 생성
    /*
     * {
     *     "body" : "댓글 내용"
     * }
     * */
    HashMap<String, String> jsonMap = new HashMap<String, String>();
    jsonMap.put("body", comment);
 
    JSONObject jsonObject = new JSONObject(jsonMap);
    String body = jsonObject.toJSONString();
 
    // ------- HTTP Request
    // (PUT)http://works.eduwill.net/rest/api/2/issue/{issueKey}/comment/${commentId}
    String uri = JIRAURI + "/rest/api/2/issue/" + key + "/comment/" + id;
    Map<String, String> map = executeHttpPut(ldapId, ldapPw, uri, body);
 
    return map;
}