본문 바로가기
spring

Spring에서 Http Request 보내기 - Feign

by 쭈꾸마뇽 2021. 9. 19.

Feign

Feign은 Netflix에서 만든 Http Client Binder이다.  Feign을 사용하면 평소에 Http 통신을 위해 사용했던 길고 긴 소스코드를 Spring Data Jpa를 사용하듯이 간편하게 사용할 수 있다.

환경 설정

plugins {
    id 'org.springframework.boot' version '2.5.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
	...
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.0.3'
}
test {
    useJUnitPlatform()
}

Spring 프로젝트를 만들고 나서 implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.0.3' 만 추가해줬다.  

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

그리고 Application 클래스에 @EnableFeignClients를 추가해줘야 한다.

테스트

Feign은 Spring Data Jpa 사용하듯이 인터페이스에 메소드를 정의해서 사용한다.

@FeignClient(value = "example", url = "http://localhost:9000/")
public interface ExampleClient {

    @GetMapping("/test")
    Object request(@RequestParam String text);
}

Localhost에 열어둔 테스트용 Api를 호출한다.  컨트롤러에서 Http Method를 매핑하드시 호출하고자 하는 Api의 메소드를 애노테이션으로 붙여주고 세부 주소를 value값으로 넣어준다.

@Test
void example1() {
    Object result = exampleClient.request("200");
    System.out.println(result);
}
@GetMapping("/test")
public ResponseEntity<String> test(@RequestParam String text, @RequestHeader Map<String, Object> requestHeader) {
    System.out.println(text);
    requestHeader.forEach((s, o) -> System.out.println(s + " : " + o));

    if (text.equals("200")) {
    	return new ResponseEntity<>(HttpStatus.OK);
    }

    return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}

요청을 받는 Api에서는 헤더정보와 들어온 RequestParam값을 출력해주었다.

정상적으로 Api가 호출되는 것을 확인할 수 있다.

Header 추가

Feign에서 Header를 추가하는 방법은 여러가지가 있다.  첫번째는 Configuration을 등록하는 것이고 또 하나는 메소드에 추가해주는 것이다.

Header Configuration

@Configuration
public class HeaderConfiguration {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            requestTemplate.header("header1", "header value 1");
            requestTemplate.header("header2", "header value 2");
        };
    }
}

@Configuration까지 해주면 모든 Feign Client에 Default로 적용된다.  만약 Default가 아닌 필요한 곳에만 Header를 추가해주고 싶다면 @Configuration을 붙이지 말고 해당 Client에 옵션을 추가해주면 된다.

@FeignClient(value = "example", url = "http://localhost:9000/", configuration = {HeaderConfiguration.class})
public interface ExampleClient {

    @GetMapping("/test")
    Object request(@RequestParam String text);
}

Method에 직접 Header 매핑

@FeignClient(value = "example", url = "http://localhost:9000/", configuration = {HeaderConfiguration.class})
public interface ExampleClient {

    @GetMapping("/test")
    Object request(@RequestParam String text);

    @GetMapping(value = "/test", headers = "header3=header value 3")
    Object request2(@RequestParam String text);
}

헤더를 추가하고 싶은 Method에 Header를 위처럼 직접 입력해 줄 수 있다.

기존에 있던 header1, header2에 header3가 추가된 것을 확인할 수 있다.

Retry

Http 통신이 실패했을 때 바로 그만둘수도 있지만 원하는 만큼 재시도 해 볼수도 있다.  Feign에서도 이 기능을 지원한다.

@Test
void example3() {
    Object result = exampleClient.request("400");
    System.out.println(result);
}

기본적으로 요청이 실패하면 그대로 실패했다는 결과를 출력한다.  실패했을 때 재시도를 위해 두가지 방법을 사용할 수 있다.

Response 사용

@GetMapping(value = "/test")
Response request3(@RequestParam String text);
@Test
void example4() {
    Response response = exampleClient.request3("400");
    System.out.println(response);
}

통신에 대한 응답값으로 Response를 사용하게 되면 예외를 출력하는 것이 아닌 Response 그대로 출력을 해준다.  이 Response 응답을 가지고 내부적으로 몇번 더 시도할지 결정해서 커스텀하게 재시도를 할 수 있다.

Retryer 사용

Feign은 Retryer를 제공한다.  요청이 실패했을 때 설정해둔 만큼 재요청을 해주는 인터페이스이다.

기본적으로 0.1초 간격으로 5번 요청을 시도하고 직접 값을 입력하여 조정할 수도 있다.

@Configuration
public class FeignRetryConfiguration {
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(1000, 2000, 3);
//        return Retryer.NEVER_RETRY;
    }
}

Retryer를 사용하기 위해 Configuration으로 등록해야 한다.  여기서 NEVER_RETRY를 사용하면 재시도를 하지 않는다.  재시도 할 때 로그를 출력하기 위해서 다음과 같이 추가 설정을 해 준다.  

public class FeignErrorDecode implements ErrorDecoder {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public Exception decode(String methodKey, Response response) {
        logger.error(format("%s 요청이 성공하지 못했습니다. status: %s requestUrl: %s, requestBody: %s, responseBody: %s",
                methodKey, response.status(), response.request().url(), Arrays.toString(response.request().body()), response.body()));

        if (isRetry(response)) {
            return new RetryableException(response.status(), format("%s 요청이 성공하지 못했습니다. Retry 합니다. - status: %s, headers: %s", methodKey, response.status(), response.headers()),
                    response.request().httpMethod(), Date.from(Instant.now()), response.request());
        }

        return new IllegalStateException(format("%s 요청이 성공하지 못했습니다. - cause: %s, headers: %s", methodKey, response.status(), response.headers()));
    }

    private boolean isRetry(Response response) {
        return response.request()
                .httpMethod()
                .name()
                .equalsIgnoreCase("GET");
    }
}

ErrorDecoder는 기본적으로 decode 함수와 isRetry 함수를 갖고 있는데 decode 함수는 요청 실패시 처리할 연산을 담당하고 isRetry는 어떤 경우에 재시도를 할지를 담당한다.  여기서는 Http 메소드가 GET인 경우에만 재시도를 하도록 했다.

public class FeignConfiguration {

	...

    @Bean
    public ErrorDecoder decoder() {
        return new FeignErrorDecode();
    }
}

그다음 이 Decode를 Bean등록해주면 설정은 끝난다.

@Test
void example5() {
    Object response = retryClient.request("400");
    System.out.println(response);
}

테스트 코드를 실행하면 3번의 재시도와 함께 연결이 실패하는 출력까지 확인할 수 있다.

요청을 받는 Api에서는 3번의 요청이 들어온 것을 확인할 수 있다.


소스 코드

 

GitHub - rkdals213/feign-client

Contribute to rkdals213/feign-client development by creating an account on GitHub.

github.com

참고 자료

 

우아한 feign 적용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요. 저는 비즈인프라개발팀에서 개발하고 있는 고정섭입니다. 이 글에서는 배달의민족 광고시스템 백엔드에서 feign 을 적용하면서 겪었던 것들에 대해서 공유 하고자 합니다

techblog.woowahan.com

'spring' 카테고리의 다른 글

Spring - Kafka 연동하기  (0) 2021.12.04
Spring Data Event Handler  (0) 2021.06.19
Custom HandlerMethodArgumentResolver  (0) 2021.06.08
JWT를 이용한 인증 처리  (0) 2021.05.29
테스트 코드 커버리지 확인하기 (jacoco) tutorial  (0) 2021.05.01

댓글