HttpUrlConnection을 이용한 외부서버 통신

프로젝트 내에서 외부 library를 사용하는데 제한이 조금 있어서 Spring이 지원하는 RestTemplate 같은 건 사용할 수 없었고, 대신 java.net.HttpUrlConnection 으로 외부 서버와 통신할 수 밖에 없었다..

 

makeParams(..) :

map에 담겨온 파라미터들을 get방식 뒤에 붙이는 url?key=value 형식으로 만드는 역할을 하는 메소드.

makeJsonParams(..) :

map에 담겨온 파라미터들을 { "key" : "value" } 와 같이 json 포맷으로 만들어 주는 메소드

httpUrlConnection(..) :

실제 외부와 connection 을 하는 메소드.

header 정보를 담아 호출해야하는 경우, json 형식으로 파라미터를 넘겨야 하는 경우 등 상황에 따라 호출하는 데이터 형식 및 호출 방식이 달라지기 때문에 오버로딩하여 구현

 

소스는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package ;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
/** @author ljpyo */
@Component("httpUtil")
public class HttpUtil {
    
    private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
    public static final String POST = "POST";
    public static final String GET = "GET";
    public static final String DELETE = "DELETE";
    
    private String makeParams(Map<String, Object> params){
        String param = null;
        StringBuffer sb = new StringBuffer();
        
        if(params != null){
           for ( String key : params.keySet() ){
               logger.info(" key : " + key + " / value : " + params.get(key));
               sb.append(key).append("=").append((params.get(key)==null?"":params.get(key)).toString().trim()).append("&");
           }
        }
        param = sb.toString().substring(0, sb.toString().length()-1);
        return param;
    }
    
    private String makeJsonParams(Map<String, Object> params){
        String json = "";
        if(params != null){
            json = new Gson().toJson(params);
        }
        return json;
    }
    
    public String httpUrlConnection(String getpost, String targetUrl, Map<String, Object> params) throws Exception {
       String returnText = this.httpUrlConnection(getpost, targetUrl, params, nullfalse);
       return returnText;
    }
    
    public String httpUrlConnection(String getpost, String targetUrl, Map<String, Object> params, boolean isJson) throws Exception {
        String returnText = this.httpUrlConnection(getpost, targetUrl, params, null, isJson);
        return returnText;
     }
    
    public String httpUrlConnection(String getpost, String targetUrl, Map<String ,Object> params, Map<String, Object> header, boolean isJson) throws Exception {
       URL url = null;
       HttpURLConnection conn = null;
       
       String jsonData = "";
       BufferedReader br = null;
       StringBuffer sb = null;
       String returnText = "";
       JSONObject jobj = null;
       
       String postParams = "";
       
       try{
           
           if(getpost.equalsIgnoreCase(POST) || getpost.equalsIgnoreCase(DELETE)){
               url = new URL(targetUrl);
           } else if(getpost.equalsIgnoreCase(GET)){
               url = new URL(targetUrl + ((params!=null)?"?"+makeParams(params):""));
           }
           logger.info("request url : " + url);
           conn = (HttpURLConnection) url.openConnection();
//         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//         conn.setRequestProperty("Accept", "application/json");
           
           if(header != null){
               conn.setRequestProperty(header.get("headerKey").toString(), header.get("headerValue").toString());
               logger.info("header : " + header.get("headerKey").toString() + "  /  headerValue : " +header.get("headerValue").toString());
           }
           
           if(isJson){
               conn.setRequestProperty("Content-Type""application/json");
           }
           
           conn.setRequestMethod(getpost);
           conn.setConnectTimeout(5000);
           conn.setReadTimeout(5000);
           conn.setDoOutput(true);
           
           if(getpost.equalsIgnoreCase(POST) || getpost.equalsIgnoreCase(DELETE)){
               if(params != null){
                   if(isJson){
                       postParams = makeJsonParams(params);
                   } else {
                       postParams = makeParams(params);
                   }
                   logger.info("isJson : " + isJson);
                   logger.info("postParam.toString()  : " + postParams);
                   logger.info("post param : " + postParams.getBytes("UTF-8").toString());
                   conn.getOutputStream().flush();
               }
           } 
           
           br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
           
           sb = new StringBuffer();
           
           while((jsonData = br.readLine()) != null){
               sb.append(jsonData);
           }
           
           returnText = sb.toString();
           
           try{
               jobj = new JSONObject(returnText);
               if! jobj.has("responseCode") ){
                   jobj.put("responseCode", conn.getResponseCode());
               }
           } catch (JSONException e){
               jobj = new JSONObject();
               jobj.put("responseCode", conn.getResponseCode());
           }
           
       } catch (IOException e){
           logger.debug("exception in httpurlconnection ! ", e);
           throw new APIException("exception in httpurlconnection !");
       } finally {
           try {
               if (br != null) br.close();
           } catch(Exception e){
               logger.warn("finally..br.close()", e);
           }
           br = null;
           try {
           if(conn!=null)
               conn.disconnect();
           } catch(Exception e){
               logger.warn("finally..conn.disconnect()", e);
           }
           conn = null;
       }
       return jobj != null ? jobj.toString() : null;
    }
    
}
 
cs

 

처음엔 GET POST만 짜놓으면 될 줄 알았는데 나중에 DELETE 방식 요청을 추가적으로 구현해야했다.

POST 처럼 날리면 될 줄 알았더니 DELETE 호출방식엔 outputstream을 사용할 수 없다는 예외가 발생하여 애 좀 먹었다.. (https://developyo.tistory.com/8 참고..) 

 

1년도 안된 신입이 짠 유틸을 꽤 큰 프로젝트에서 공통으로 사용하고 있으니 불안해 죽겠다.

아직 큰 문제 없는 걸 보니 그냥 잘 돌아가고 있는듯...

 

일단 본 프로젝트에선 connectionTimeout , readTimeout Exception이 발생했을 때 재시도(retry) 없이 Customizing한 Exception을 내뱉으며 접속을 종료 시키지만

공부할 겸 retry 기능을 넣어봐야겠다.

 

추후 retry 기능을 넣어 재포스팅하겠다.

 

 

* RETRY (재시도) 설정 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package ;
 
@Component("httpUtil")
public class HttpUtil {
    
    public String httpUrlConnection(~) throws Exception {
               
       //재시도 추가 190220
       for(int i=0; i < (retry<1?1:retry); i++){
               
           try{
               //파라미터 세팅
               //호출 
               //생략~               
 
               if(conn.getResponseCode() != HttpStatus.OK){
                    //응답코드가 실패인 경우 Exception 고의로 발생(catch 에서 continue 로 처리)
                    throw new CustomException();    //customized exception 사용
              }  
              //성공은 for문 나감
              break;
             
              //응답 값 파싱
           } catch (SocketTimeoutException ste){
               errMsg = ste.getMessage();
               logger.debug(errMsg);
           } catch (CustomExceptione ce){
               errMsg = ce.getMessage();
               logger.debug(errMsg);
           } catch (Exception e){
               
           } finally {
               //자원 해제
               try {
                   if (br != null) br.close();
               } catch(Exception e){
                   logger.warn("finally..br.close()", e);
               }
               br = null;
               try {
               if(conn!=null)
                   conn.disconnect();
               } catch(Exception e){
                   logger.warn("finally..conn.disconnect()", e);
               }
               conn = null;
           }
       }
       
       if(jobj!=null){
           return jobj.toString();
       } else {
           throw new APIException(errMsg, ConstantsAPI.APIResult.E_NETWORK.getCode());
       }
    }
}
cs

 

호출받는 쪽에 connect가 오래걸려 connectTimeOut 이 나거나, connect는 되었으나 내부 처리가 오래걸려 readTimeOut이 발생한 경우 특정 횟수(n)만큼 재시도를 하도록 소스를 조금 수정했다.

(보통 한번 안되면 몇 번을 다시 시도해도 안되는 것 같지만...)

 

retry 횟수를 파라미터로 받고,

for문을 돌린다.

timeout 예외 및 응답코드가 200이 아닌 경우(CustomException)가 발생하면( SocketTimeoutException ) catch 문에서 Error 를 먹어버리고 for문이 retry 횟수만큼 이어서 진행된다.

응답코드가 200인 경우 break; 구문을 타게 되어 for문이 종료한다.

* 예외가 발생하지 않을 경우 return 하고 해당 메소드는 끝남.

 

 

요청하고 있는 외부 API 에서 HttpStatus 를 제멋대로 (보통 200을 사용) 담아 리턴하고 있어서 소스 전체를 올리진 못했다. 

(결과코드를 일일히 매핑해야했기 때문에.. 소스가 요상함)

 

여튼 for문 써서 connection 객체로부터 getResponseCode()로 연결상태 확인 하여 처리하면 된다.

반응형

+ Recent posts