티스토리 뷰

Spring Frameworks

Spring boot - API Gateway, Zuul 예제

까오기 까오기 2019. 3. 17. 12:43

1. Overview 

ZUUL은 넷플릿스에서 사용 하는 JVM 기반의 라우터로 마이크로 서비스에서 라우팅, 모니터링, 에러처리, 보안 등을 담당한다.

출처 : https://medium.com/netflix-techblog/announcing-zuul-edge-service-in-the-cloud-ab3af5be08ee


만약 GATEWAY를 직접 개발해야 한다면 ... 

- 요청 메소드(GET/POST/PUT/DELETE)에 따라 값을 처리하는 것을 구현해야 한다. 

- 요청할 때 값은 KEY, VALUE의 파라미터 형태일 수도 있고 REQUEST BODY 형태일 수도 있다. 

- 요청에 따라 전달 하는 API 서버에 주소로 전달을 해야 한다. 

- API 주소는 "/USER/{USER_ID}" 이런식으로 동적으로 만들어야 할 수도 있다. 

- 요청/결과에 대한 로그를 관리해야 할 수 있다. 

- 에러에 대한 처리가 필요하다. 

- 인증 처리, 모니터링, 서비스 제어 등등을 모두 구현해야 한다. 


직접 처음부터 개발할려면 할게 참 많습니다. 


ZUUL PROXY가 하는 일 

- URL 패턴에 따라 자동으로 API SERVER에 전달해준다. 

- 전처리, 후처리, 추가처리, 에러처리에 대해 손쉽게 개발할 수 있게 도와준다. 

2. DEPENDENCY 설정(Gradle) 

1
2
3
4
5
6
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
cs

3. Application 설정

@EnableZuulProxy 및 필터 추가 

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
@SpringBootApplication
@EnableZuulProxy
public class ZuulDemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ZuulDemoApplication.class, args);
    }
 
    @Bean
    public PreFilter preFilter() {
        return new PreFilter();
    }
    
    @Bean
    public PostFilter postFilter() {
        return new PostFilter();
    }
    
    @Bean
    public ErrorFilter errorFilter() {
        return new ErrorFilter();
    }
    
    @Bean
    public RouteFilter routeFilter() {
        return new RouteFilter();
    }
}
cs

4. 필터 설정

출처 : https://medium.com/netflix-techblog/announcing-zuul-edge-service-in-the-cloud-ab3af5be08ee


<Filter>

Filter Description

Pre filter

요청 전 처리 필터

인증 처리, 헤더에서 토큰을 읽어서 유효성 체크를 한다. 

요청 로그, 서비스 제한 등 처리 

Custem filter

추가 처리 필터

Routing filter

라우팅 필터

조건에 따라 특정 서버로 전달하고자 할 때 이곳에 로직 구현.   

Post filter

후 처리 필터

결과 로그, 서비스 모니터링 처리 

Error filter 

에러 처리 필터

에러에 따른 공통 처리

 

샘플 코드 

PreFilter.java

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
public class PreFilter extends ZuulFilter {
 
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    @Override
    public String filterType() {
        return "pre";
    }
 
    @Override
    public int filterOrder() {
        return 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
 
        log.debug("Request Method : " + request.getMethod());
        log.debug("Request URL : " + request.getRequestURL().toString());
 
        String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (!validateToken(authorizationHeader)) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseBody("API key not authorized");
            ctx.getResponse().setHeader("Content-Type""text/plain;charset=UTF-8");
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
    
    private boolean validateToken(String tokenHeader) {
        // do something to validate the token
        return true;
    }
}
cs


RouteFilter.java

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
public class RouteFilter extends ZuulFilter {
 
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    @Override
    public String filterType() {
        return "route";
    }
 
    @Override
    public int filterOrder() {
        return 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() {
        log.debug("Route Filter");
        return null;
    }
}
cs




PostFilter.java

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
public class PostFilter extends ZuulFilter {
 
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    @Override
    public String filterType() {
        return "post";
    }
 
    @Override
    public int filterOrder() {
        return 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() {
        log.debug("Post Filter");
 
        return null;
    }
}
cs


ErrorFilter.java

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
public class ErrorFilter extends ZuulFilter {
 
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    @Override
    public String filterType() {
        return "error";
    }
 
    @Override
    public int filterOrder() {
        return 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return RequestContext.getCurrentContext().getThrowable() != null;
    }
 
    @Override
    public Object run() {
        Throwable throwable = RequestContext.getCurrentContext().getThrowable();
        log.error("Exception was thrown in filters: ", throwable);
        return null;
    }
}
cs

5. application.properties 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#Zuul routes. "/user-service" 이 경로로 유입되는 것은 http://localhost:8083 서버로 연결한다. 
zuul.routes.user-service.path=/user-service/**
zuul.routes.user-service.url=http://localhost:8083
 
# 예외, 무시 처리 
zuul.ignored-services='*'
zuul.ignored-patterns=/**/admin/**
 
# 타임아웃 설정   
zuul.host.connect-timeout-millis=3000
zuul.host.socket-timeout-millis=3000
 
#Ribbon 사용 안함. 
ribbon.eureka.enabled=false
 
# 서버 포트 설정 
server.port=8080
cs

6. 에러 페이지 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class CustomErrorController implements ErrorController {
 
    private static final String ERROR_PATH = "/error";
 
    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
    
    @RequestMapping(ERROR_PATH)
    public Map<StringString> handleError(HttpServletRequest request, HttpServletResponse response) {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(status.toString()));
        Map<StringString> errorMsg = new HashMap<>();
        errorMsg.put("code", status.toString());
        errorMsg.put("msg", httpStatus.getReasonPhrase());
        return errorMsg;
    }
 
}
cs

404 에러 페이지 호출 시 결과 화면

{"msg":"Not Found","code":"404"}

7. API 서비스 개발 : User 서비스 


Spring Starter Project로 간단한 서비스 개발 

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
@RestController
public class UserTestController {
 
    private final Logger log = LoggerFactory.getLogger(getClass());
 
    @Autowired 
    private UserService userService; 
    
    @GetMapping("/users/{userId}")
    public User getUser(User pUser) {
        return userService.getUser(pUser);
    }
 
    @PostMapping("/users")
    public User addUser(User pUser) {
        userService.addUser(pUser);
        return pUser;
    }
 
    @PutMapping("/users/{userId}")
    public String modifyUser(@RequestBody String jsonStr, User pUser) {
        log.debug(jsonStr);
        //userService.modifyUser(pUser);
        return jsonStr;
    }
 
    @DeleteMapping("/users/{userId}")
    public User removeUser(User pUser) {
        userService.removeUser(pUser);
        return pUser;
    }
    
}
cs

application.properties
1
2
#web server
server.port=8083
cs

8. 테스트 

User 서비스 Run  

http://localhost:8083/users/1234

1234 고객 정보 조회 확인 


Gateway 서비스를 실행 및 호출 

http://localhost:8080/user-service/users/1234

user-service : http://localhost:8083 서버로 연결 된다. 


참고


댓글
댓글쓰기 폼