diff --git a/document/gateway/gateway_filter.md b/document/gateway/gateway_filter.md new file mode 100644 index 0000000..f5ed52c --- /dev/null +++ b/document/gateway/gateway_filter.md @@ -0,0 +1,220 @@ +이번 장에서는 [Spring Cloud Gateway - 구성](https://imprint.tistory.com/215) 에 이어 직접 API Gateway에 Filter를 적용하는 방법들에 대해서 알아본다. +모든 소스 코드는 [깃허브 (링크)](https://github.com/roy-zz/spring-cloud) 에 올려두었다. + +--- + +### 자바 코드로 적용 + +1. Gateway 모듈에 구성을 위한 FilterConfig 클래스 파일을 생성한다. + +"/test-server-1/**" 경로로 들어오는 요청의 헤더에 "test-server-1-request" 키와 "test-server-1-request-header" 값을 가지는 헤더를 추가하고 TEST-SERVER-1 이라는 이름을 가진 애플리케이션에 리다이렉트 한다. +"/test-server-2/**" 경로로 들어오는 요청의 헤더에 "test-server-2-request" 키와 "test-server-2-request-header" 값을 가지는 헤더를 추가하고 TEST-SERVER-2 라는 이름을 가진 애플리케이션에 리다이렉트 한다. + +```java +@Configuration +public class FilterConfig { + @Bean + public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) { + return builder.routes() + .route(r -> r.path("/test-server-1/**") + .filters(f -> f.addRequestHeader("test-server-1-request", "test-server-1-request-header") + .addResponseHeader("test-server-1-response", "test-server-1-response-header")) + .uri("lb://TEST-SERVER-1")) + .route(r -> r.path("/test-server-2/**") + .filters(f -> f.addRequestHeader("test-server-2-request", "test-server-2-request-header") + .addResponseHeader("test-server-2-response", "test-server-2-response-header")) + .uri("lb://TEST-SERVER-2")) + .build(); + } +} +``` + +2. Eureka Client의 컨트롤러를 수정한다. + +헤더의 정보를 출력하도록 test-server1, test-server2 모듈의 컨트롤러를 수정한다. + +**test-server1** +```java +@Slf4j +@RestController +@RequestMapping(value = "/test-server-1") +public class TestController { + @GetMapping("/welcome") + public String welcome(@RequestHeader("test-server-1-request") String requestHeader) { + log.info("{}", requestHeader); + return "Welcome to the Test Server - 1"; + } +} +``` + +**test-server2** +```java +@Slf4j +@RestController +@RequestMapping(value = "/test-server-2") +public class TestController { + @GetMapping("/welcome") + public String welcome(@RequestHeader("test-server-2-request") String requestHeader) { + log.info("{}", requestHeader); + return "Welcome to the Test Server - 2"; + } +} +``` + +브라우저를 켜고 localhost:8000/test-server-1/welcome과 localhost:8000/test-server-2/welcome에 접속해본다. +각 모듈의 출력 결과는 아래와 같다. +**test-server1** +```bash +INFO 85192 --- [o-auto-1-exec-1] c.r.s.t.controller.TestController : test-server-1-request-header +``` +**test-server2** +```bash +INFO 85202 --- [o-auto-1-exec-1] c.r.s.t.controller.TestController : test-server-2-request-header +``` + +Response 헤더에도 정상적으로 입력되었는지 브라우저에서 개발자 모드를 실행시키고 확인해본다. + +![](image/check-custom-header.png) + +만약 자바 코드로 filter를 적용하고 싶지 않다면?! +아래와 같이 application.yml 파일을 수정해서 필터를 적용해도 동일하게 작동한다. + +```yaml +spring: + application: + name: gateway-service + cloud: + gateway: + routes: + - id: test-server-1 + uri: lb://TEST-SERVER-1 + predicates: + - Path=/test-server-1/** + filters: + - AddRequestHeader=test-server-1-request, test-server-1-request-header + - AddResponseHeader=test-server-1-response, test-server-1-response-header + - id: test-server-2 + uri: lb://TEST-SERVER-2 + predicates: + - Path=/test-server-2/** + filters: + - AddRequestHeader=test-server-2-request, test-server-2-request-header + - AddResponseHeader=test-server-2-response, test-server-2-response-header +``` + +--- + +### Custom Filter + +1. CustomFilter 클래스를 생성한다. + +AbstractGatewayFilterFactory를 상속받은 클래스를 생성한다. +apply메서드를 확인해보면 요청처리 전에 필터를 처리하는 부분과 요청처리 후에 필터를 처리하는 부분이 나뉘어 있는 것을 확인할 수 있다. + +```java +@Slf4j +@Component +public class CustomFilter extends AbstractGatewayFilterFactory { + public CustomFilter() { + super(Config.class); + } + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + // Pre process start ==================================================== + ServerHttpRequest request = exchange.getRequest(); + ServerHttpResponse response = exchange.getResponse(); + log.info("Custom pre process filter: request uri -> {}", request.getId()); + // Pre process end ======================================================= + // Post process start ======================================================= + return chain.filter(exchange).then(Mono.fromRunnable(() -> { + log.info("Custom post process filter: response code -> {}", response.getStatusCode()); + })); + // Post process end ======================================================= + }; + } + public static class Config { + // Put the configuration properties + } +} +``` + +2. application.yml 수정 + +우리가 생성한 CustomFilter로 등록될 수 있도록 filters 항목에 CustomFilter를 등록한다. + +```yaml +spring: + application: + name: gateway-service + cloud: + gateway: + routes: + - id: test-server-1 + uri: lb://TEST-SERVER-1 + predicates: + - Path=/test-server-1/** + filters: + - CustomFilter + - id: test-server-2 + uri: lb://TEST-SERVER-2 + predicates: + - Path=/test-server-2/** + filters: + - CustomFilter +``` + +3. 유레카 클라이언트의 컨트롤러를 수정한다. + +Filter 연동을 위해 수정한 부분은 없으며 단지 이전 테스트와 컨트롤러가 겹치는 것을 방지하기 위함이다. + +**test-server-1** +```java +@Slf4j +@RestController +@RequestMapping(value = "/test-server-1") +public class TestController { + @GetMapping("/custom-filter") + public String customFilter() { + return "Custom filter with test server 1"; + } +} +``` + +**test-server-2** +```java +@Slf4j +@RestController +@RequestMapping(value = "/test-server-2") +public class TestController { + @GetMapping("/custom-filter") + public String customFilter() { + return "Custom filter with test server 2"; + } +} +``` + +4. 정상작동 확인 + +브라우저를 켜고 localhost:8000/test-server-1/custom-filter와 localhost:8000/test-server-2/custom-filter에 접속해본다. +출력된 결과는 아래와 같다. + +```bash +c.r.s.gateway.filter.CustomFilter : Custom pre process filter: request uri -> baf12948-2 +c.r.s.gateway.filter.CustomFilter : Custom post process filter: response code -> 200 OK +c.r.s.gateway.filter.CustomFilter : Custom pre process filter: request uri -> baf12948-3 +c.r.s.gateway.filter.CustomFilter : Custom post process filter: response code -> 200 OK +``` + +정상적으로 우리가 원하는 결과가 출력되는 것을 확인할 수 있다. +~~(설마 c.r.s가 com.roy.springcloud의 약자인가...왜 처음알았지)~~ + +--- + + + + + +--- + +참고한 강의: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4 \ No newline at end of file diff --git a/document/gateway/image/check-custom-header.png b/document/gateway/image/check-custom-header.png new file mode 100644 index 0000000..b32eab0 Binary files /dev/null and b/document/gateway/image/check-custom-header.png differ diff --git a/gateway/build.gradle b/gateway/build.gradle index aed0bd5..209b3c1 100644 --- a/gateway/build.gradle +++ b/gateway/build.gradle @@ -5,6 +5,8 @@ ext { dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-gateway' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/gateway/src/main/java/com/roy/springcloud/gateway/config/FilterConfig.java b/gateway/src/main/java/com/roy/springcloud/gateway/config/FilterConfig.java new file mode 100644 index 0000000..770c4d7 --- /dev/null +++ b/gateway/src/main/java/com/roy/springcloud/gateway/config/FilterConfig.java @@ -0,0 +1,21 @@ +package com.roy.springcloud.gateway.config; + +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; + +// @Configuration +public class FilterConfig { + // @Bean + public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) { + return builder.routes() + .route(r -> r.path("/test-server-1/**") + .filters(f -> f.addRequestHeader("test-server-1-request", "test-server-1-request-header") + .addResponseHeader("test-server-1-response", "test-server-1-response-header")) + .uri("lb://TEST-SERVER-1")) + .route(r -> r.path("/test-server-2/**") + .filters(f -> f.addRequestHeader("test-server-2-request", "test-server-2-request-header") + .addResponseHeader("test-server-2-response", "test-server-2-response-header")) + .uri("lb://TEST-SERVER-2")) + .build(); + } +} diff --git a/gateway/src/main/java/com/roy/springcloud/gateway/filter/CustomFilter.java b/gateway/src/main/java/com/roy/springcloud/gateway/filter/CustomFilter.java new file mode 100644 index 0000000..631a45b --- /dev/null +++ b/gateway/src/main/java/com/roy/springcloud/gateway/filter/CustomFilter.java @@ -0,0 +1,37 @@ +package com.roy.springcloud.gateway.filter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +@Slf4j +@Component +public class CustomFilter extends AbstractGatewayFilterFactory { + public CustomFilter() { + super(Config.class); + } + + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + // Pre process start ==================================================== + ServerHttpRequest request = exchange.getRequest(); + ServerHttpResponse response = exchange.getResponse(); + log.info("Custom pre process filter: request uri -> {}", request.getId()); + // Pre process end ======================================================= + // Post process start ======================================================= + return chain.filter(exchange).then(Mono.fromRunnable(() -> { + log.info("Custom post process filter: response code -> {}", response.getStatusCode()); + })); + // Post process end ======================================================= + }; + } + + public static class Config { + // Put the configuration properties + } +} diff --git a/gateway/src/main/resources/application.yml b/gateway/src/main/resources/application.yml index 2caedaa..0d90b54 100644 --- a/gateway/src/main/resources/application.yml +++ b/gateway/src/main/resources/application.yml @@ -18,7 +18,15 @@ spring: uri: lb://TEST-SERVER-1 predicates: - Path=/test-server-1/** + filters: + # - AddRequestHeader=test-server-1-request, test-server-1-request-header + # - AddResponseHeader=test-server-1-response, test-server-1-response-header + - CustomFilter - id: test-server-2 uri: lb://TEST-SERVER-2 predicates: - - Path=/test-server-2/** \ No newline at end of file + - Path=/test-server-2/** + filters: + # - AddRequestHeader=test-server-2-request, test-server-2-request-header + # - AddResponseHeader=test-server-2-response, test-server-2-response-header + - CustomFilter \ No newline at end of file diff --git a/test-server1/src/main/java/com/roy/springcloud/testserver1/controller/TestController.java b/test-server1/src/main/java/com/roy/springcloud/testserver1/controller/TestController.java index 403a4f4..31838ce 100644 --- a/test-server1/src/main/java/com/roy/springcloud/testserver1/controller/TestController.java +++ b/test-server1/src/main/java/com/roy/springcloud/testserver1/controller/TestController.java @@ -1,16 +1,25 @@ package com.roy.springcloud.testserver1.controller; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Slf4j @RestController @RequestMapping(value = "/test-server-1") public class TestController { @GetMapping("/welcome") - public String welcome() { + public String welcome(@RequestHeader("test-server-1-request") String requestHeader) { + log.info("{}", requestHeader); return "Welcome to the Test Server - 1"; } + @GetMapping("/custom-filter") + public String customFilter() { + return "Custom filter with test server 1"; + } + } diff --git a/test-server2/src/main/java/com/roy/springcloud/testserver2/controller/TestController.java b/test-server2/src/main/java/com/roy/springcloud/testserver2/controller/TestController.java index 0c6489e..cddd9ed 100644 --- a/test-server2/src/main/java/com/roy/springcloud/testserver2/controller/TestController.java +++ b/test-server2/src/main/java/com/roy/springcloud/testserver2/controller/TestController.java @@ -1,16 +1,25 @@ package com.roy.springcloud.testserver2.controller; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Slf4j @RestController @RequestMapping(value = "/test-server-2") public class TestController { @GetMapping("/welcome") - public String welcome() { + public String welcome(@RequestHeader("test-server-2-request") String requestHeader) { + log.info("{}", requestHeader); return "Welcome to the Test Server - 2"; } + @GetMapping("/custom-filter") + public String customFilter() { + return "Custom filter with test server 2"; + } + }