jungpyo9@gmail.com

github.com/develo-pyo 

많이 부족한 백엔드 개발자입니다.

 

어설프게 알고 넘어갔던 부분은 추후 추가적으로 학습하고 보완하기 위해,

문제를 경험하고 해결했던 방법이 있다면 공유와 기록을 하기 위해 블로그를 하고 있습니다.

 

얕은 지식으로 작성한 글이 많아 잘못된 정보가 다수 존재 할 수 있으니, 이 부분은 감안하시어 읽어주시면 좋겠습니다.

혹여 게시글을 읽으실 때 잘못된 내용이 보이시거나, 공유해주실 수 있는 지식이 있다면 댓글 혹은 메일 부탁드립니다.

 

감사합니다.

반응형

Jboss 이미지 경로 설정 (정적 컨텐츠 경로 지정)

 

jboss home directory/standalone/configuration/ 경로에 위치한

standalone.xml 를 수정해준다.

standalone.xml 에 

아래의 설정을 추가해 준다.

<location name="/images" handler="Images"/>
<file name="Images" path="/usr/local/images" directory-listing="true"/>
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  <subsystem xmlns="urn:jboss:domain:undertow:2.0">
            <buffer-cache name="default"/>
            <server name="default-server">
                <http-listener name="default" socket-binding="http"/>
                <host name="default-host" alias="localhost">
                    <location name="/" handler="welcome-content"/>
                    <location name="/images" handler="Images"/>
                    <filter-ref name="server-header"/>
                    <filter-ref name="x-powered-by-header"/>
                </host>
            </server>
            <servlet-container name="default">
                <jsp-config development="true" check-interval="2" modification-test-interval="2" recompile-on-fail="true"/>
                <websockets/>
            </servlet-container>
            <handlers>
                <file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
                <file name="Images" path="/usr/local/images" directory-listing="true"/>
            </handlers>
            <filters>
                <response-header name="server-header" header-name="Server" header-value="WildFly/9"/>
                <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>
            </filters>
</subsystem>
cs

domain/images/sample.jpg와 같이 /images 경로가 포함된 호출이 들어오면

/usr/local/images (WAS가 설치된 경로를 기준 D:에 설치되어있다면 D:~)경로에서 파일을 찾는다.

 

[설정 확인]

설정을 마쳤으면

http://localhost:8082/images/ 를

호출해본다.

위와 같은 화면이 나오면 성공.

 

[설정확인2]

/usr/local/images 경로에

.html, .jpg, .css 등의 정적 파일들을 올려주고

 

jboss 서버 호출시 파일을 가져오는 모습을 볼 수 있다.

ex) http://domain/images/파일명

 

 

참고 : https://developer.jboss.org/thread/258975

반응형

Roy Fielding 의 2000년 논문에 의해 소개. 
웹의 장점을 최대한 활용할 수 있는 네트워크 기반의 아키텍쳐, Representational state transfer (REST)  

3가지요소 (리소스메소드메시지)로 구성

HTTP POST, http://test/user/ 

"id":"developyo", 
"blog":"https://developyo.tistory.com/" 

위 요청은 POST 메소드(create), 생성 대상이 되는 리소스인 http://test/user/ 
생성하고자 하는 사용자 내용은 JSON 을 사용 

[HTTP 메소드]

POST,GET,PUT,DELETE 만 사용 

POST : create, Idempotent X 
GET : select, Idempotent O 
PUT : update, Idempotent O 
DELETE : delete, Idempotent O 

POST, GET, PUT, DELETE 는 CRUD 역할 
Idempotent 한 메소드(GET, PUT, DELETE)는 수행 실패시 한 번 더 호출하면 되지만 Idempotent 하지 않은 메소드(POST(create))는 실패시 트랜잭션 처리에 주의해야한다. 


※ Idempotent 는 여러번 호출되도 같은 실행결과가 나오는 것을 의미한다. 
예를 들어 a++는 호출시마다 값이 증가하므로 Idempotent 하지 않으며, a=1 은 호출시 값이 같으므로 Idempotent 하다. 

[REST의 리소스]

REST는 리소스 지향 아키텍쳐 스타일 답게 모든 것을 리소스(명사)로 표현, 각 세부 리소스에는 id 를 붙임 
즉 사용자라는 리소스 타입을 http://test/user 라고 정의했다면 
developyo 란 id를 갖는 리소스는 http://test/user/developyo 와 같이 표현 

POST/생성 의 예 :

HTTP POST, http://test/user/ 

"id":"developyo", 
"blog":"https://developyo.tistory.com/" 

GET/조회 의 예 :

HTTP GET, http://test/user/developyo 

PUT/수정 의 예 :

HTTP PUT, http://test/user/developyo 

"id":"developyo", 
"blog":"https://developyo2.tistory.com/" 

DELETE/삭제 의 예 :

HTTP DELETE, http://test/user/developyo 

 

[REST의 특징]

1. 유니폼 인터페이스(Uniform interface) : 
특정 언어나 기술에 종속받지 않으며 HTTP와 JSON(혹은 XML)을 사용할 수 있는 모든 플랫폼에서 사용이 가능한 느슨한 결합 형태의 구조 
2. 무상태성(stateless) : 클라이언트 상태를 서버쪽에 유지하지 않는다는 의미 (참고)
3. 캐싱 가능 
4. 자체 표현 구조 : 직관적인 이해가 가능 
5. 클라이언트 서버 구조 

[REST 안티 패턴]

1. GET/POST 터널링 : http://test/user?id=developyo&method=update (GET 터널링의 예)
2. 자체 표현 구조가 아닌 경우 (위 1번과 같은 경우) 
3. response code 사용하지 않음 :
response code 는 항상 200(성공) 으로 응답하며, 실제 성공/실패 리턴코드를 body 에 담아 따로 리턴하는 경우

 

 

참고 : https://bcho.tistory.com/953

참고 : https://blog.npcode.com/2017/04/03/rest%EC%9D%98-representation%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80/

 

반응형

맥주소 MAC ADDRESS 확인하기

 

1. 윈도우키+r

2. cmd 입력

3. ipconfig /all 입력 (아래 참고)

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
C:\Users\user>ipconfig /all
 
Windows IP 구성
 
   호스트 이름 . . . . . . . . : JP
   주 DNS 접미사 . . . . . . . :
   노드 유형 . . . . . . . . . : 혼성
   IP 라우팅 사용. . . . . . . : 아니요
   WINS 프록시 사용. . . . . . : 아니요
 
이더넷 어댑터 이더넷:
 
   미디어 상태 . . . . . . . . : 미디어 연결 끊김
   연결별 DNS 접미사. . . . :
   설명. . . . . . . . . . . . : Qualcomm Atheros AR8161 PCI-E Gigabit Ethernet Controller (NDIS 6.30)
   물리적 주소 . . . . . . . . : 99-99-99-99-99-99
   DHCP 사용 . . . . . . . . . : 아니요
   자동 구성 사용. . . . . . . : 예
 
 
무선 LAN 어댑터 Wi-Fi:
 
   연결별 DNS 접미사. . . . :
   설명. . . . . . . . . . . . : Realtek RTL8723AE Wireless LAN 802.11n PCI-E NIC
   물리적 주소 . . . . . . . . : 99-99-99-99-99-99
   DHCP 사용 . . . . . . . . . : 예
   자동 구성 사용. . . . . . . : 예
   링크-로컬 IPv6 주소 . . . . : 
   IPv4 주소 . . . . . . . . . : ...
   서브넷 마스크 . . . . . . . : ...
   임대 시작 날짜. . . . . . . : 
   임대 만료 날짜. . . . . . . : 
   기본 게이트웨이 . . . . . . : 
   DHCP 서버 . . . . . . . . . : 
   DHCPv6 IAID . . . . . . . . : 
   DHCPv6 클라이언트 DUID. . . : 
   DNS 서버. . . . . . . . . . : 
                                 
   Tcpip를 통한 NetBIOS. . . . : 사용
 
cs

유선으로 인터넷을 사용하는 경우 : 이더넷 영역의 물리적 주소가 맥어드레스

와이파이로 인터넷을 사용하는 경우 : 무선 LAN 어댑터 영역의 물리적 주소가 맥어드레스

 

반응형

[ IP 클래스 ]

IPv4 ( 32 bit 주소체계 )

IPv6 (128 bit 주소체계)

 

네트워크 / 호스트

네트워크 주소는 대표주소

호스트 주소는 하나의 네트워크 밑의 피씨를 구분하기 위한 주소

0???????.????????.????????.???????? A클래스 (0~127.x.x.x)

10??????.????????.????????.???????? B클래스 (128~191.x.x.x)

11??????.????????.????????.???????? C클래스 (192~255.x.x.x)

 

* 브로드캐스트(같은 네트워크 안의 모든 피씨에 데이터 전송)는 IP 대역에서 가장 끝번호 

 

ex)

192.125.62.0 은 C클래스. 192.125.62.0 는 네트워크 주소

192.125.62.1 는 PC 2번의 호스트 주소

192.125.62.2 는 PC 2번의 호스트 주소

192.125.62.3 는 PC 2번의 호스트 주소

192.125.62.255 는 브로드캐스트 IP

 

 

[ 서브넷 마스크 ]

IP 대역을 나누기 위함.

비트연산 AND 연산(하나라도 0이면 0)으로 0을 만들 수 있는 수로 서브넷 마스크 사용

 

ex)

192.125.62.0 C 클래스를 2개의 네트워크 대역으로 나누고 싶은 경우,

서브넷마스크로 192.125.62.128 을 사용 

 

네트워크 주소 192.125.62.0 -> 11000000.01111101.00111110.00000000

서브넷마스크 192.125.62.128 -> 11111111.11111111.11111111.10000000

네트워크 대역1 (192.125.62.0~192.125.62.127): 11000000.01111101.00111110.00000000 ~ 01111111네트워크 대역2 (192.125.62.127~192.125.62.255): 11000000.01111101.00111110.10000000 ~ 11111111

* 네트워크 대역1 의 브로드캐스트 IP 는 192.125.62.127(11000000.01111101.00111110. 01111111)

* 네트워크 대역2 의 브로드캐스트 IP 는 192.125.62.255(11000000.01111101.00111110. 11111111)

 

ex2)

192.125.62.0 C 클래스를 4개의 네트워크 대역으로 나누고 싶은 경우, 

서브넷마스크로 192.125.62.192(11111111.11111111.11111111.11000000) 을 사용 

 

ex3)

192.125.62.0 C 클래스를 8개의 네트워크 대역으로 나누고 싶은 경우, 

서브넷마스크로 192.125.62.192(11111111.11111111.11111111.11100000) 을 사용 

반응형

Mybatis like 조건시 문자열 처리

 

 

특정 문자열을 포함한 값을 가져오는 쿼리는

select * from where title like '%대상문자열%'

과 같이 쿼리를 작성 및 사용하는데,

이를 mybatis 에서 사용시 like 뒷 부분을 어떻게 처리해야 할지 난해하다.
*
like '%#{key}%' 와 같이 작성시
아래와 같은 에러로그를 볼 수 있다.

19:16:37,814 INFO  [stdout] (default task-3) ### SQL: SELECT COUNT(*)      FROM banword     WHERE ban_word like '%?%'

 

19:16:37,814 INFO  [stdout] (default task-3) ### Cause: java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).

19:16:37,814 INFO  [stdout] (default task-3) ; SQL []; Parameter index out of range (1 > number of parameters, which is 0).; nested exception is java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0)., result_cd=0} | RESULT = fail | REMOTEADDR = 127.0.0.1 | PORT = 58841 | TIME = 350 ms

 

해결 :

 

[MySQL]
title like CONCAT('%',#{key},'%')

[Oracle]
title like '%' ||  #{key} || '%'

[MSSQL]
title like '%' + #{key} + '%' 


 

반응형

'back > Mybatis,Ibatis' 카테고리의 다른 글

[mybatis] xml에서 java method 사용  (0) 2020.08.24
[Mybatis] 동적쿼리 (if test) 문자열처리  (4) 2020.03.27
[Ibatis] dtd 경로 문제  (1) 2019.12.30
[Mybatis] $과 # 차이  (0) 2019.12.05

Exception 클래스의 구현과 AOP 설정을 이용한 예외처리

 

API 서버 개발을 하며 케이스별 에러코드와 에러메시지를 response 값으로 던져주어야 했다.

각각의 API(Controller)에 try catch 로 exception 을 잡아 상황별 에러코드 및 에러메시지를 던지기엔 API 갯수가 너무 많았고, 비효율적(노가다)이라 생각하여

enum과 customized Exception class, AOP 설정을 통해 해결했다.

 

1. ENUM 클래스 작성
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
public static enum APIResult {
        SUCCESS (200"success"),
        FAILURE (999"fail"),  //기타 에러
        E_PARAM_NULL(101"input parameter is null"),  // 필수값 누락 에러
        E_FORMAT(102"input parameter data format error"), // 파싱 에러
        E_NETWORK(104"network error"), //네트워크 에러
        
        private int code;
        private String message;
        
        private APIResult(int _code, String _message) {
            this.code = _code;
            this.message = _message;
        }
        
        public static APIResult search(int code) {
            for (APIResult status : APIResult.values()) {
                if(status.getCode() == code) {
                    return status;
                }
            }
            return null;
        }
      
        public int getCode() {
            return code;
        }
 
        public void setCode(int code) {
            this.code = code;
        }
 
        public String getMessage() {
            return message;
        }
 
        public void setMessage(String message) {
            this.message = message;
        }
}
cs
예외 케이스별로 던져줄 Error Code와 Error Message를 Enum 클래스로 작성.
 

2. customized Exception class 작성

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
package ;
 
public class APIException extends RuntimeException {
 
    private int ERR_CODE = APIResult.FAILURE.getCode();
 
    public APIException()  {
        super();
    }
 
    public APIException(String errDesc) {
        super(errDesc);
    }
 
    public APIException(String errDesc, int code) {
        super(errDesc);
        this.ERR_CODE = code;
    }
 
    public APIException(String errDesc, ConstantsAPI.APIResult e){
        super(errDesc);
        this.ERR_CODE = e.getCode(); 
    }  
 
    public APIException(ConstantsAPI.APIResult e){
        super(e.getMessage());
        this.ERR_CODE = e.getCode(); 
    }
 
    public String getErrorDesc() {
        return this.getMessage();
    }
 
    public int getErrCode() {
        return this.ERR_CODE;
    }  
 
}
 
 
cs

RuntimeException 을 상속받는 Exception 클래스를 작성한다.

생성자 오버로딩을 하여 Exception Message 및 Exception Code를 받을 수 있게 한다.

* Enum 클래스를 통으로 받는 생성자 또한 만들어준다.

ex)

APIException(ConstantsAPI.APIResult e){

//생략

}

 
3. Controller 에서 try catch 처리
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
@RequestMapping(value = "/sample", method={RequestMethod.POST}, produces = "application/json; charset=UTF-8")
public ModelAndView sample(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> inParam) throws Exception {
    
    Map<String, Object> result = new HashMap<String, Object>();
    ModelAndView mav = new ModelAndView("jsonView");
    
    try{
        // inParam 유효성 체크
        CommUtil.paramVaildate(new String[] {"tokenId"}, inParam);
        
        sampleService.sampleMethod(inParam);
        
        result.put("result_cd", APIResult.SUCCESS.getCode());
        result.put("result_msg""");
    } catch (APIException ae) {
        result.put("result_cd", ae.getErrCode());
        result.put("result_msg", ae.getMessage());
    } catch (Exception e){
        result.put("result_cd", APIResult.FAILURE.getCode());
        result.put("result_msg", e.getMessage());
    }
    
    mav.addAllObjects(result);
    
    return mav;
}
 
cs
9라인 CommUtil.paramValidate(..); : API 서버의 Controller 이기 때문에 필수값이 제대로 들어왔는지 우선 확인
11라인 sampleService.samplemethod(..); : 서비스 호출(서비스 내부적으로 파싱 및 기타 에러가 나는 경우 try catch로 예외를 잡아 Controller 쪽으로 예외를 미룸..  (생략)
catch문으로 예외를 잡아 error code 및 error message result에 담아 return..

 

* input parameter 유효성 검사

 

향상된 for문을 적절히 사용.

Map에 키가 담겨있지 않거나, 빈 값인 경우 구현해 놓았던 Custom Exception 클래스를 throw.

이때, 위에 작성해 놓은 enum을 활용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static boolean paramValidate(String[] params, Map<String, Object> inParam) {
        
        boolean _result = true;
        
        try {
            for (String item : params) {
                if(!inParam.containsKey(item)) {
                    throw new APIException(String.format("%s not exist", item), APIResult.E_PARAM_NULL);
                }
                if(String.valueOf(inParam.get(item)).isEmpty) {
                    throw new APIException(String.format("%s is null", item), APIResult.E_PARAM_NULL);
                }
            }
        } catch(APIException e) {
            throw e;
        } catch(Exception e) {
            throw new APIException(APIResult.FAILURE);
        }
        
        return _result;
    }
}
 
cs

 

여기까지만 해도 괜찮지만 Controller 마다 아래의 코드가 들어가는게 마음에 들지 않았다..

1
2
3
4
5
6
7
8
9
try{
 
catch (APIException ae) {
    result.put("result_cd", ae.getErrCode());
    result.put("result_msg", ae.getMessage());        
catch (Exception e){
    result.put("result_cd", APIResult.FAILURE.getCode());
    result.put("result_msg", e.getMessage());
}
cs

 

 

6줄 남짓의 코드를 지우기 위해 

Exception을 공통(일괄?)처리 할 수 있는 @ControllerAdvice 및 @Exceptionhandler 를 사용하여 Exception 처리를 해보았으나..

Exception이 발생하는 경우, log를 찍기 위해 구현해 놓았던 interceptor 가 제대로 동작하지 않는 문제점이 발생.

 

구글링을 해보니, Exception 발생시 interceptor의 postHandle은 타지 않는단다..

 

아래는 구글링으로 찾은 내용..

 

From the javadoc of HandlerInterceptor.postHandle

Intercept the execution of a handler. Called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view. Can expose additional model objects to the view via the given ModelAndViewDispatcherServlet processes a handler in an execution chain, consisting of any number of interceptors, with the handler itself at the end. With this method, each interceptor can post-process an execution, getting applied in inverse order of the execution chain.

Arguably the MethodArgumentNotValidException is thrown before the method is actually called, it is called in preparing the actual method call. 

Actually the postHandle is only executed after successful execution/invocation of the method.  In case of an exception only the preHandle and afterCompletion methods are called.

>> postHandle은 정상적인 메소드 실행에서만 작동. 예외시 preHandle, afterCompletion 만 호출됨.

 

(출처 : https://stackoverflow.com/questions/20551888/controlleradvice-methods-seem-to-bypass-interceptors)

 

그래서 AOP를 사용해보았다.

 

 

4. AOP 사용

4-1) pom.xml 에 aop 사용을 위한 설정 추가

 

property에 아래의 내용 추가

1
<org.aspectj-version>1.6.10</org.aspectj-version>
cs

 

dependency 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
 
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
 
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
cs

 

4-2) SampleAOP 작성

@Around(pointCut)을 선언

@Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.*Controller.*(..))")

설명 >> public 접근제한자이면서, org.springframework.web.servlet.ModelAndView 리턴타입이면서, 

            com.sample. 밑의 Controller로 끝나는 모든 Class 밑의 모든 메소드(인자값 상관없이)가 실행될 때, 해당 함수(around(..))를 전, 후로 실행...

 Controller(pjp.proceed()) 에서 예외 발생시 AOP에서 error code 및 error message를 넣어 return

 

* advice : @Around, @Before, @After 등..  해당 어노테이션이 붙은 코드의 실행 시점을 의미

* pointCut : 정규식을 사용하여 어떤 joinPoint 를 사용할지 지정

* joinPoint : advice가 적용될 수 있는 메소드(함수)

 

* xml 빈 등록 잘못 할 경우 aop 를 두번 타는 문제가 발생할 수 있으니 주의.

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
package ;
 
import java.util.HashMap;
import java.util.Map;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
 
@Component
@Aspect
public class SampleAOP {
    
    @Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.*Controller.*(..))")
    public ModelAndView around(ProceedingJoinPoint pjp) {
        ModelAndView mav = null;
        
        try {
              mav = (ModelAndView) pjp.proceed();
        } catch(APIException e){
            mav = new ModelAndView("jsonView");
            e.printStackTrace();
            Map<String, Object> result = new HashMap<String, Object>();
 
            result.put("result_cd", e.getErrCode());
            result.put("result_msg", e.getMessage());
            
            mav.addAllObjects(result);
        } catch (Throwable e) {
            mav = new ModelAndView("jsonView");
            e.printStackTrace();
            Map<String, Object> result = new HashMap<String, Object>();
 
            result.put("result_cd"0);
            result.put("result_msg", e.getMessage());
            
            mav.addAllObjects(result);
        }
        return mav;
    }
}
 
cs

 

 

5. Controller 에서 try catch 처리 수정

 

예외처리를 AOP에서 일괄적으로 관리하니 기존의 try catch 를 지워준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 @RequestMapping(value = "/sample", method={RequestMethod.POST}, produces = "application/json; charset=UTF-8")
    public ModelAndView sample(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> inParam) throws Exception {
        
        Map<String, Object> result = new HashMap<String, Object>();
        ModelAndView mav = new ModelAndView("jsonView");
        
        // inParam 유효성 체크
        CommUtil.paramVaildate(new String[] {"tokenId"}, inParam);
            
        sampleService.sampleMethod(inParam);
            
        result.put("result_cd", APIResult.SUCCESS.getCode());
        result.put("result_msg""");
        
        mav.addAllObjects(result);
        
        return mav;
    }
 
cs

 

AOP 를 어설프게나마 사용해 보았다는 걸로 만족...

AOP 는 추후 더 공부해서 개념, 설정, 구현 부분을 따로 포스팅 해보겠다.

 

 

 

* 제 경우 기존 프로젝트에 aop 설정이 부분적으로 되어있었습니다.

  위의 설정 및 소스를 그대로 따라할 경우 aop 동작이 제대로 안될 수도 있습니다.

 

반응형



https://colorscripter.com/


반응형

'etc.' 카테고리의 다른 글

[etc.] nativeQuery 복붙할 때 따옴표(") 제거하기  (0) 2023.04.19
Slack reminder 푸시 설정  (0) 2023.04.07
[IDE] IntelliJ 단축키  (0) 2022.07.11

postman 을 사용하면 여러모로 편리하지만,

리눅스환경에서 외부 api를 호출해야 하는 경우, 아래와 같이 curl 로 호출해보면 된다.

 

1
2
3
4
5
6
7
8
9
1.GET 요청
curl -X GET --data-urlencode "key=value&key2=value2" http://ip:port
 
2.POST 요청
curl -X POST http://ip:port -d '{"objKey":"objValue", "arrayKey":["arrayValue1", "arrayValue2"]}'
 
3.DELETE 요청
curl -X DELETE http://ip:port/key
 
cs

 

[ 요청시 헤더 추가 ]

-H "Content-Type: application/json"

 

[ 요청시 connection timeout ]

--connect-timeout : 타임아웃설정을 걸고 싶을 경우

(따로 해당 옵션을 주지 않아도 default로 일정 시간이 적용되는 것 같다. hang이 알아서 풀린다..)

ex) curl --connect-timeout 300 -X GET ~

 

[ 응답시 헤더 확인 ]

-I : response 헤더정보 확인할 때 사용

(요청에 따른 return 값 없이 500, 404, 200 등의 responseCode만 리턴되는 경우 헤더정보를 보고 확인)

ex) curl -I -X DELETE http://ip:port/key

 

[ 요청 및 응답 세부 정보 확인 ]

-v : 요청한 파라미터 및 리턴 responseCode 등 상세한 request/response 정보를 보고 싶을 때 사용

 

반응형

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