[Spring Cloud] Gateway 샘플

by 스뎅(thDeng) on

Gateway 가 필요할 때 간단한 설정으로 사용할 수 있는 Spring Cloud Gateway가 있다. Public 망과 private 망을 서로 연결해 주는 역할을 하거나, 일부 host의 요청만 거르거나, 일부 path의 호출만 허용하는 등의 필터링 역할도 할 수 있다. 그리고 서킷 브레이커와 통합도 쉽고, 트래픽 제한 등의 역할도 가능하다. Spring WebFlux 위에서 동작하기 때문에 성능도 상당히 좋다.

샘플

설정은 아래처럼 정말 간단하다. 이 샘플은 Kotlin으로 작성되어 있고, Kotlin을 사용한다면 DSL 스타일로 쓸 수 있기 때문에 눈에도 잘 들어오고 편하다.

import org.springframework.beans.factory.annotation.Value
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
import org.springframework.cloud.gateway.route.builder.filters
import org.springframework.cloud.gateway.route.builder.routes
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders.AUTHORIZATION
import org.springframework.http.HttpMethod.GET

/**
 * MyApi routing 설정
 */
@Configuration
internal class MyApiRoutingConfiguration constructor(
    /**
     * 포워딩할 서버
     */
    @Value("\${my-api.url}")
    private val myApiUrl: String,

    /**
     * 포워딩할 서버의 인증 정보 `username`
     */
    @Value("\${my-api.username}")
    private val myApiUsername: String,

    /**
     * 포워딩할 서버의 인증 정보 `password`
     */
    @Value("\${my-api.password}")
    private val myApiPassword: String,
) {

    @Bean
    fun myApiRouteLocator(builder: RouteLocatorBuilder) = builder.routes {
        // /v1/users 로의 routing 설정
        route(id = "my-api-user") {
            method(GET) and // `and`가 꼭 필요하다.
//              host("") and // 호출 받을 `host` 설정도 가능하다.
                path("$MY_API_PREFIX/v1/users") // 정규식 패턴을 사용해도 된다.
            filters {
                // path에 gateway 에서 사용할 prefix가 있으면 잘라낼 단위
                stripPrefix(PREFIX_SIZE)
                // 포워딩할 서버에서 인증이 필요한 경우 인증을 추가할 수 있다.
                addRequestHeader(AUTHORIZATION, (myApiUsername to myApiPassword).toBasicAuthHeaderValue())
            }
            uri(adServerApiUrl)
        }
        route(id = "my-api-animal") { /* ... */ }
        route(id = "my-api-building") { /* ... */ }
    }

    companion object {
        private const val MY_API_PREFIX = "/api/my-api"
        private const val PREFIX_SIZE = 2
    }
}

// 아래 extension은 다른 파일에 선언해 두고 공통으로 사용하면 된다.
/**
 * [Pair]를 Basic auth 에 사용될 인증 정보 header 값으로 인코딩한다.
 * - `Pair#first`: username
 * - `Pair#second`: password
 */
internal fun Pair<String, String>.toBasicAuthHeaderValue(): String =
    """Basic ${String(Base64.getEncoder().encode("$first:$second".toByteArray(UTF_8)), UTF_8)}"""

위의 gateway 설정은 아래처럼 포워딩 하는 설정이다. {my-api} 는 설정에서 읽어온 my-api.url 값이다.

method, host, path 등으로 gateway 에서 호출 받을 url을 지정한다.

filters 로 포워딩해 줄 서버를 호출하는데 필요한 정보를 설정한다. stripPrefix 는 아래처럼 호출된 path 에서 몇 개의 prefix를 제거할 것인지 지정하는 것이다. 샘플에서는 2를 지정해서 앞부분의 2개(/api/my-api)부분을 제거했다. 헤더에 추가로 값을 설정하고 싶은 경우에는 addRequestHeader 를 사용하면 된다, 샘플에서는 Basic auth 인증을 추가했다.

GET http://localhost:8080/api/my-api/v1/users

->

GET http://{my-api}/v1/users
Authorization: Basic aaabbbcccddd

설정된 Route 확인하기

내가 원하는 형태로 route가 설정되었는지 확인이 필요하다. 설정이 잘못된 경우 오류 메시지도 없기 때문에 설정을 잘못한건지 테스트 호출을 잘못 한건지 정말 답답하다. 이럴 때 gateway actuator를 사용하면 확인이 수월하다.

management:
  endpoints:
    web:
      exposure:
        include: gateway
  endpoint:
    gateway:
      enabled: true # default: true

application.yml 파일에 위와 같이 gateway 설정을 추가하면 actuator 에서 route 설정을 확인할 수 있다. 운영 환경에서는 노출되면 안 되기 때문에 actuator는 비운영 환경에서만 설정해 두도록 한다. management.endpoint.gateway.enabled 설정은 default 값이 true 이기 때문에 설정할 필요는 없지만, 운영환경 등 노출을 하고 싶지 않은 환경에서는 false 로 설정이 가능하다. Route를 마음대로 추가/삭제할 수 있기 때문에 테스트 환경에서만 쓰는 것을 강력하게 추천한다!!

http://localhost:8080/actuator/gateway/routes

위의 주소로 접속하면, 아래처럼 설정된 모든 route를 확인할 수 있다.

[
  {
    "predicate": "(Methods: [GET] && Paths: [/api/my-api/v1/users], match trailing slash: true)",
    "route_id": "my-api-user",
    "filters": [
      "[[StripPrefix parts = 2], order = 0]",
      "[[AddRequestHeader Authorization = 'Basic aaabbbcccddd'], order = 0]"
    ],
    "uri": "http://{my-api}",
    "order": 0
  },
  {
    "predicate": "(Hosts: [kotlin.abc.org] && Paths: [/image/png], match trailing slash: true)",
    "route_id": "test-kotlin",
    "filters": [
      "[[PrefixPath prefix = '/httpbin'], order = 0]",
      "[[AddResponseHeader X-TestHeader = 'foobar'], order = 0]"
    ],
    "uri": "http://localhost:8080",
    "order": 0
  }
]

Actuator를 통해서 route 를 추가하거나 변경할 수도 있다. (위험해 위험해..) 아래 목록은 gateway actuator 설정에서 오픈되는 endpoint이다.

참고

별도로 명시하지 않을 경우, 이 블로그의 포스트는 다음 라이선스에 따라 사용할 수 있습니다: Creative Commons License CC Attribution-NonCommercial-ShareAlike 4.0 International License