아래는 1년전 쯤 개발했던 httpUrlConnection 모듈이다

https://developyo.tistory.com/10?category=688588

 

시간이 지날 수록 소스는 누더기가 되어가고..

디자인패턴을 공부하고, 객체지향스러운 개발을 위해 이책 저책(클린코드 도서 등) 읽고나니

위 코드가 리펙토링이 필요하다는 것을 느껴 리펙토링을 해보았다.

 

상황]

1. 연동되는 서버가 계속해서 늘어났다.

2. 각각의 연동 서버에 맞춰야 되는 예외상황들이 늘어났다. (ex: timeout, charset, retry, responsecode..)

3. 예외상황들을 처리 하기 위한 파라미터들을 받기 위해 메소드의 인자값이 늘어났다.

4. 오버로딩되는 메소드가 늘어났다.

 

방향]

1. 객체를 받도록 처리하자. 

: 파라미터는 적을 수록 좋고,  파라미터가 너무 많이지면 객체를 파라미터로 받도록 소스를 리펙토링 해야한다.

2. boolean 값을 받는 메소드 1개는 두개의 메소드 따로 만들자. (도서 클린코드 참고)

: requestApi(boolean isJsonFormat, ...) 와 같이 boolean 값을 파라미터로 받은 후 분기처리 처리하지 말고

requestApiJsonFormat(...), requestApi(...) 와 같이 두개의 메소드로 분리해야 한다. (도서 클린코드 참고)

3. 필수값과 optional 값을 구분하기 위해 builder 패턴을 적용하여 객체를 설계해보자.

4. 전처리 후처리 로깅을 proxy 패턴을 적용하여 처리해보자. 

5. junit 을 사용하여 테스트해보자. (junit 관련 포스팅1, junit 관련 포스팅2)

 

1. RequestForm

httpUrlConnection 호출에 필요한 파라미터 정보 객체

빌더패턴을 적용하여 필수 파라미터(목적지주소, http method)는 생성자로,

그외 optional 파라미터는 setter 로 받도록 했다.

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
package com.jpp.web.util.HttpUtil;
 
import java.util.Map;
 
public class RequestForm {
   
   private final int tryCount;
   private final int connectionTimeOut;
   private final int readTimeOut;
   private final String httpMethod;
   private final String url;
   private final Map<String, Object> header;
   private final int httpStatus;
   private final String requestCharset;
   private final String responseCharset;
   
   public static class Builder {
      private String httpMethod; //required
      private String url;        //required
      private int tryCount = 1;  //optional
      private int connectionTimeOut = 1000;  //optional
      private int readTimeOut = 1000;     //optional
      private Map<String, Object> header; //optional
      private int httpStatus = 0;    //optional
      private String requestCharset = "UTF-8";
      private String responseCharset = "UTF-8";
      
      public Builder(String httpMethod, String url) {
         this.httpMethod = httpMethod;
         this.url = url;
      }
      
      public Builder setTryCount(int tryCount) {
         this.tryCount = tryCount<1?1:tryCount;
         return this;
      }
      
      public Builder setConnectionTimeOut(int connectionTimeOut) {
         this.connectionTimeOut = connectionTimeOut<100?1000:connectionTimeOut;
         return this;
      }
      
      public Builder setReadTimeOut(int readTimeOut) {
         this.readTimeOut =  readTimeOut<100?1000:readTimeOut;
         return this;
      }
      
      public Builder setHeader(Map<String, Object> header) {
         this.header = header;
         return this;
      }
      
      public Builder setExpectedHttpStatus(int httpStatus) {
         this.httpStatus = httpStatus;
         return this;
      }
      
      public Builder setRequestCharset(String requestCharset) {
         this.requestCharset = requestCharset;
         return this;
      }
      
      public Builder setResponseCharset(String responseCharset) {
         this.responseCharset = responseCharset;
         return this;
      }
      
      public RequestForm build() {
         return new RequestForm(this);
      }
   }
   
   public RequestForm(Builder builder) {
      this.httpMethod = builder.httpMethod;
      this.url = builder.url;
      this.tryCount = builder.tryCount;
      this.connectionTimeOut = builder.connectionTimeOut;
      this.readTimeOut = builder.readTimeOut;
      this.header = builder.header;
      this.httpStatus = builder.httpStatus;
      this.requestCharset = builder.requestCharset;
      this.responseCharset = builder.responseCharset;
   }
   
   public int getTryCount() {
      return tryCount;
   }
 
   public int getConnectionTimeOut() {
      return connectionTimeOut;
   }
 
   public int getReadTimeOut() {
      return readTimeOut;
   }
 
   public String getHttpMethod() {
      return httpMethod;
   }
 
   public String getUrl() {
      return url;
   }
   
   public Map<String, Object> getHeader(){
      return header;
   }
   
   public int getHttpStatus() {
      return httpStatus;
   }
 
   public String getRequestCharset() {
      return requestCharset;
   }
 
   public String getResponseCharset() {
      return responseCharset;
   }
   
}
 
cs

 

 

2. CustomHttpUrlConnection

java.net.HttpUrlConnection 객체를 사용하여 외부 서버 api를 호출하는 역할을 하는 클래스

1) 요청정보를 담고있는 RequestForm객체를 파라미터로 받도록 했다.

2) jsonFormat(body로 호출) 여부인 boolean 값을 파라미터로 받아 내부에서 분기처리하는게 아닌, 별도의 메소드로 각각 분리했다.

메소드를 작은 단위로 쪼개면 코드 중복이 많이 줄어들 것 같은데 나중에..

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package com.jpp.web.util.HttpUtil;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
 
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import com.google.gson.Gson;
import com.jpp.web.comm.CustomException;
import com.jpp.web.constants.Constants;
import com.jpp.web.constants.ConstantsEnum;
 
public class CustomHttpUrlConnection {
   
   private static final Logger logger = LoggerFactory.getLogger(CustomHttpUrlConnection.class);
   private static final String UTF_8 = "UTF-8";
 
   private int responseCode = 0;
   private long startTime = 0;
 
   private RequestForm reqForm;
   private Map<String, Object> reqParams;
   
   public CustomHttpUrlConnection(RequestForm reqForm, Map<String, Object> reqParams){
      this.reqForm = reqForm;
      this.reqParams = reqParams;
   }
   
   public int getResponseCode() {
      return responseCode;
   }
   
   public long getStartTime() {
      return startTime;
   }
   
   public String requestApi() {
      
      this.startTime = System.currentTimeMillis();
      
      String httpMethod = reqForm.getHttpMethod();  
      String surl = reqForm.getUrl();               
      int tryCnt = reqForm.getTryCount();   
      int readTimeOut = reqForm.getReadTimeOut(); 
      int connectionTimeOut = reqForm.getConnectionTimeOut();
      Map<String, Object> header = reqForm.getHeader();
      int expectedHttpStatus = reqForm.getHttpStatus();
      String requestCharset = reqForm.getRequestCharset();
      String responseCharset = reqForm.getResponseCharset();
      
      String reqCharset = requestCharset==null||requestCharset.isEmpty()?UTF_8:requestCharset;
      String resCharset = responseCharset==null||responseCharset.isEmpty()?UTF_8:responseCharset;
             
      URL url = null;
      HttpURLConnection conn = null;
      BufferedReader br = null;
      JSONObject jobj = null;
      String postParams = "";
      String errMsg = "";
      String returnText = "";
 
      
      for(int i=0; i < tryCnt; i++){
          
         try {
             
            if(httpMethod.equalsIgnoreCase(Constants.POST) || httpMethod.equalsIgnoreCase(Constants.DELETE)){
               url = new URL(surl);
            } else if(httpMethod.equalsIgnoreCase(Constants.GET)){
               url = new URL(surl + ((reqParams!=null)?"?"+makeUrlEncodedParams(reqParams, reqCharset):""));
            }
            
            conn = (HttpURLConnection) url.openConnection();
            
            if(header != null){
                for(String key : header.keySet()) {
                    conn.setRequestProperty(key, header.get(key)!=null?header.get(key).toString():"");
                }
            }
             
            conn.setRequestMethod(httpMethod);
            conn.setConnectTimeout(connectionTimeOut);
            conn.setReadTimeout(readTimeOut);
            conn.setDoOutput(true);
            
            if(httpMethod.equalsIgnoreCase(Constants.POST) || httpMethod.equalsIgnoreCase(Constants.DELETE)){
               if(reqParams != null){
                  postParams = makeUrlEncodedParams(reqParams, reqCharset);
                  conn.getOutputStream().write(postParams.getBytes(UTF_8));
                  conn.getOutputStream().flush();
               }
            }
            
            this.responseCode = conn.getResponseCode();
            
            if(expectedHttpStatus > 0){
               if(expectedHttpStatus!=conn.getResponseCode()){
                  throw new CustomException("successCode : {" + expectedHttpStatus + "}" + " , responseCode : {" + this.responseCode + "}", ConstantsEnum.API_RESULT.E_NETWORK.getCode());
               }
            }
            
            br = new BufferedReader(new InputStreamReader(conn.getInputStream(),resCharset));
            
            StringBuffer sb = null;
            sb = new StringBuffer();
            
            String jsonData = "";
            while((jsonData = br.readLine()) != null){
               sb.append(jsonData);
            }
            returnText = sb.toString();
            
            try{
               jobj = new JSONObject(returnText);
            } catch (JSONException e){
               throw new CustomException();
            }
            
            break;
            
         } catch (SocketTimeoutException se){
            logger.error("connection fail : " + se);
             errMsg = se.getMessage();
         } catch (CustomException e){
            logger.error("response fail : " + e);
             errMsg = e.getMessage();
         } catch (Exception e){
            throw new CustomException(e.getMessage().toString(), ConstantsEnum.API_RESULT.E_NETWORK.getCode());
         } 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 CustomException(errMsg, ConstantsEnum.API_RESULT.E_NETWORK.getCode());
      }
   }
   
   
   public String requestApiWithJsonForm() {
      
      this.startTime = System.currentTimeMillis();
      
      String httpMethod = reqForm.getHttpMethod();
      int tryCnt = reqForm.getTryCount();
      int readTimeOut = reqForm.getReadTimeOut();
      int connectionTimeOut = reqForm.getConnectionTimeOut();
      String surl = reqForm.getUrl();
      Map<String, Object> header = reqForm.getHeader();
      int expectedHttpStatus = reqForm.getHttpStatus();
      String responseCharset = reqForm.getResponseCharset();
      
      String resCharset = responseCharset==null||responseCharset.isEmpty()?UTF_8:responseCharset;
      
      URL url = null;
      HttpURLConnection conn = null;
      BufferedReader br = null;
      JSONObject jobj = null;
      String postParams = "";
      String errMsg = "";
      String returnText = "";
       
      for(int i=0; i < (tryCnt<1?1:tryCnt); i++){
          
         try {
             
            url = new URL(surl);
            
            conn = (HttpURLConnection) url.openConnection();
            
            if(header != null){
                for(String key : header.keySet()) {
                    conn.setRequestProperty(key, header.get(key)!=null?header.get(key).toString():"");
                }
            }
             
            conn.setRequestProperty("Content-Type""application/json");
            conn.setRequestMethod(httpMethod);
            conn.setConnectTimeout(connectionTimeOut);
            conn.setReadTimeout(readTimeOut);
            conn.setDoOutput(true);
            
            if(reqParams != null){
                postParams = makeJsonParams(reqParams);
                conn.getOutputStream().write(postParams.getBytes(UTF_8));
                conn.getOutputStream().flush();
            }
            
            this.responseCode = conn.getResponseCode();
            
            if(expectedHttpStatus != 0){
               if(expectedHttpStatus!=conn.getResponseCode()){
                  throw new CustomException("successCode : {" + expectedHttpStatus + "}" + " , responseCode : {" + this.responseCode + "}", ConstantsEnum.API_RESULT.E_NETWORK.getCode());
               }
            }
            
            br = new BufferedReader(new InputStreamReader(conn.getInputStream(), resCharset));
            
            StringBuffer sb = null;
            sb = new StringBuffer();
            
            String jsonData = "";
            while((jsonData = br.readLine()) != null){
               sb.append(jsonData);
            }
            returnText = sb.toString();
            
            try{
               jobj = new JSONObject(returnText);
            } catch (JSONException e){
               throw new CustomException();
            }
            
            break;
            
         } catch (SocketTimeoutException se){
            logger.error("connection fail : " + se);
             errMsg = se.getMessage();
         } catch (CustomException e){
            logger.error("response fail : " + e);
             errMsg = e.getMessage();
         } catch (Exception e){
            throw new CustomException(e.getMessage().toString(), ConstantsEnum.API_RESULT.E_NETWORK.getCode());
         } 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 CustomException(errMsg, ConstantsEnum.API_RESULT.E_NETWORK.getCode());
      }
   }
    
   
   private String makeUrlEncodedParams(Map<String, Object> params, String charset) throws Exception{
      String param = "";
      StringBuffer sb = new StringBuffer();
      
      if(params != null){
         for ( String key : params.keySet() ){
             try {
                sb.append(key).append("=").append((params.get(key)==null?"":URLEncoder.encode(params.get(key).toString(), charset)).toString().trim()).append("&");
             } catch (UnsupportedEncodingException e) {
                logger.error("ex while encoding : {}", e.getMessage());
                throw e;
             }
         }
         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;
   }
   
}
 
 
cs

 

 

3. HttpUtil

클라이언트에서 실제로 사용될 클래스

1) 프록시(proxy) 패턴을 적용하여 CustomHttpUrlConnection객체의 requestApi(), requestApiWithJsonForm() 메소드를 호출하기 전과 후에 logging 메소드가 호출되도록 했다

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
package com.jpp.web.util.HttpUtil;
 
import java.util.Map;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class HttpUtil {
    
   private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
   
   private RequestForm reqForm;
   private Map<String, Object> reqParams;
   private CustomHttpUrlConnection httpUrlConnection;
   
   public HttpUtil(RequestForm reqForm, Map<String, Object> reqParams) {
      this.reqForm = reqForm;
      this.reqParams = reqParams;
      httpUrlConnection = new CustomHttpUrlConnection(reqForm, reqParams);
   }
   
   public String requestApi() {
      printBeforeLog();
      String resParams = httpUrlConnection.requestApi();
      printAfterLog(resParams);
      return resParams;
   }
   
   public String requestApiWithJsonForm() {
      printBeforeLog();
      String resParams = httpUrlConnection.requestApiWithJsonForm();
      printAfterLog(resParams);
      return resParams;
   }
   
   private void printBeforeLog() {
      logger.info("HTTPCONNECTION.REQUEST|URL:{}|IN_PARAMS:{}"
            , reqForm.getUrl(), reqParams);
   }
   
   private void printAfterLog(String resParams) {
      logger.info("HTTPCONNECTION.RESPONSE|URL:{}|TIME:{}|STATUS_CODE:{}|IN_PARAMS:{}|OUT_PARAMS:{}"
            , reqForm.getUrl()
            , System.currentTimeMillis() - httpUrlConnection.getStartTime()
            , httpUrlConnection.getResponseCode()<1?"":httpUrlConnection.getResponseCode()
            , reqParams
            , resParams);
   }
   
}
 
cs

 

4. test

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
package com.jpp.web.util.HttpUtil;
 
import java.util.HashMap;
import java.util.Map;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpUtilTest {
   
   private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
   
   @Test
   public void test() {
      RequestForm reqForm = new RequestForm.Builder("GET""127.0.0.1:8082/mobile/device")
            .setConnectionTimeOut(1000)
            .setReadTimeOut(1000)
            .setTryCount(3)
            .build();
      
      Map<String, Object> reqParams = new HashMap<String, Object>();
      reqParams.put("deviceType""1");
      reqParams.put("osVersion""10");
      
      String apiResult = new HttpUtil(reqForm, reqParams).requestApiWithJsonForm();
      logger.info(apiResult);
   }
 
   @Test
   public void test2() {
      RequestForm reqForm = new RequestForm.Builder("GET""127.0.0.1:8082/mobile/version")
            .setConnectionTimeOut(1000)
            .setReadTimeOut(1000)
            .setTryCount(3)
            .build();
      
      Map<String, Object> reqParams = new HashMap<String, Object>();
      
      String rs = new HttpUtil(reqForm, reqParams).requestApi();
      logger.info(rs);
   }
}
 
cs

 

소스리뷰를 받고싶다..

 

모든 코드는 제 github(https://github.com/develo-pyo/boot-mybatis)에 올려놓았습니다.

반응형

+ Recent posts