[NODE,NEST] SERVICE_KEY_IS_NOT_REGISTERED_ERROR 해결

 

요즘 node랑 nest 공부 하면서 간단히 이것저것 만들어 보고 있다가.

공공데이터 포탈에서 어떤 API를 가져오려고 작업을 했었다.

그런데 간헐적으로 SERVICE_KEY_IS_NOT_REGISTERED_ERROR  에러가 자꾸 발생을 하는 

현상을 발견했다.

<cmmMsgHeader>
    <errMsg>SERVICE ERROR</errMsg>
    <returnAuthMsg>SERVICE_KEY_IS_NOT_REGISTERED_ERROR</returnAuthMsg>
    <returnReasonCode>30</returnReasonCode>
</cmmMsgHeader>

 

이런 오류가 자꾸 떨어지는데 

분명 포스트맨에서 테스트 할때는 쭉 정상으로 작동을 했는데 

node서버에서 돌릴땐 어떨땐 정상으로 받아오고 어떨땐 저 에러가 발생을 하는 현상이 있어

이것저것 해봤는데 결국은 인코딩 문제였다. 

왜 간헐적으로 정상응답과 오류응답이 나타나는건지 몰라 한참을 해매다가

그냥 차라리 인코딩을 하지 말고 보내 보자 하고 해보니 저 에러가 나타나지 않고 정상작동 하는것을 확인 했다.

 

// firstValueFrom 함수를 사용하여 Observable을 Promise로 변환
      const response = await firstValueFrom(
        this.httpService.get(
          {
            params: queryParams,
            responseType: 'text', // XML 응답을 처리하기 위해 responseType을 'text'로 설정
          },
        ),
      );

이런식으로 요청을 보내고 있었는데 

확인해보니 

 

HttpService (Axios 기반)를 사용할 때, URL 파라미터는 자동으로 인코딩이 된다고 한다.

Axios는 내부적으로 encodeURIComponent 함수를 사용하여 URL 파라미터의 항목들을 인코딩하기 때문에,

별도로 인코딩 처리를 하지 않아도 된다고 한다.

기존에는 당연히 인코딩해서 보내줘야지 하고 parmas에 보내는 값중 키값을

const encodedApiKey = encodeURIComponent(serviceKey); // API 키 인코딩

이렇게 사용하고 있었는데 

자동으로 인코딩이 한번 더 되버리니 키값에 문제가 생긴것인다.

저 인코딩 해주는 부분을 빼고 디코드 키를 바로 사용하면 해결이 된다.

그럼 그냥 전부 다 오류응답으로 나와야 하는데 왜 되다 안되다 하는것인지는 모르겠다;;;

 

 

Cannot find module 'msw/node' 에러 해결

 

NEXT.js 14버전 환경에서 jest를 사용한 테스트 환경 구축중에 

api요청등의 문제를 모킹서버로 해결하기 위해 

msw설치후 테스트를 돌렸는데 

 

msw/node 를 찾지 못한다는 에러가 발생을 하였다.

분명 패키지제이슨엔 설치가 되어 있는데도 오류가 발생하고 있어 

chatGpt에 물어봐도 다시 설치하라는 말만 하고 있어 다른 방법을 찾아 보던중

 

https://github.com/mswjs/msw/issues/1786

 

"Cannot find module 'msw/node'" in Jest JSDOM environment · Issue #1786 · mswjs/msw

Prerequisites I confirm my issue is not in the opened issues I confirm the Frequently Asked Questions didn't contain the answer to my issue Environment check I'm using the latest msw version I'm us...

github.com

이곳의 글을 확인 하였고 코멘트에서 해결방법을 찾았다.

 

jest.config.ts 파일의 설정중에 

testEnvironmentOptions: {
    customExportConditions: [""],
  },

이부분을 추가 하면 된다. 

에러가 난 이유를 설명한걸 해석해 보자면

 

JSDOM이 내보내기 조건을 강제하기 때문입니다 browser.

즉, JSDOM은 "타사 패키지가 browser필드를 내보내는 경우 해당 필드를 사용하십시오"라고 말합니다.

그것이 기본값이고 다소 위험한 기본값입니다. 왜? JSDOM은 여전히 ​​Node.js에서 실행되기 때문입니다 .

게다가 JSDOM은 설계상 100% 브라우저 호환성을 가질 수 없으므로 browser내보내기 조건을 강제하면

MSW와 같이 다양한 환경에 대해 다양한 코드를 제공하는 패키지로 작업할 때 테스트가 필요 이상으로 실패하게 됩니다.

이렇게(위와 같은 설정을) 하면 JSDOM이 올바른 동작인 node(또는 ) 내보내기 조건을 사용하게 됩니다 .default

이번 변경 이후 다른 수입 관련 문제가 발생하는 경우 관련이 없으므로 별도로 처리해야 합니다.

모든 사람이 따를 수 있도록 이 권장 사항을 마이그레이션 가이드에도 추가합니다.

 

라고 한다. 

org.springframework.web.reactive.function.client.WebClientRequestException: readAddress(..) failed: Connection reset by peer; 오류 해결

 

현재 프론트엔드로 개발하고 있는데 간헐적으로 서버를 통해 외부 API를 요청할때 오류가 발생하는것을 확인 했다.

여기저기 찾아보니 

WebClinet를 사용해서 외부에 요청을 보낼때 대상서버의 연결이 닫혔을때 나타나는 오류 라고 한다. 

일단 의심이 가는 시나리오는

우리쪽 A서버에서 외부 B서버로 API 요청을 보내고 커넥션을 맺을때 B서버가 중간에 재실행 혹은

별도의 문제로 서버다운이 발생하는 경우라고 생각하여 해당 업체에 문의해본 결과

서버가 내려간적은 없다고 한다. 

 

두번째 시나리오는 B서버가 로드밸런서를 사용해 A서버에서 최초 요청시 B-1에 커넥션을 맺고 통신을 하다

다시 요청시에는 B-2 서버로 요청을 하는 경우

세번째는 로드밸런서 자체의 타임아웃이 A서버보다 빠른 경우

 

확인을 위해 해당 업체에 문의한결과 로드밸런서를 사용중이며 타임아웃이 10분이라고 한다.

 

오류 재현을 위해 테스트해보니 최초 요청 이후 10분후 재요청을 했을 경우에만 동일한 오류가 나는것을 확인하고

세번째 문제가 이유라고 판단 하였고 

다른분들이 작성한 깃이나 블로그의 도움을 받아 수정 할수 있도록 백엔드 팀에 전달하였다.

문제 해결을 위해 참고한 블로그및 사이트들이다.

참고 사이트

https://velog.io/@youngerjesus/Connection-Reset-by-Peer-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0

 

Connection Reset by Peer 문제 해결

Client 가 요청을 보냈는데 서버쪽에서 연결이 닫혔다고 다시 연결하라는 RST (Reset) 패킷을 보내는 경우에 이 에러가 발생한다.Connection prematurely closed BEFORE response 이렇게 쓰기도 한다. Client-Server 연

velog.io

https://jskim1991.medium.com/spring-boot-how-to-solve-webclient-connection-reset-by-peer-error-b1fa38e4106a

 

[Spring Boot] How to solve WebClient Connection reset by peer error

I had a requirement to fetch user data from an external system. It was implemented using WebClient as part of declarative http client…

jskim1991.medium.com

https://github.com/reactor/reactor-netty/issues/1774

 

Connection reset by peer exception · Issue #1774 · reactor/reactor-netty

We have a micro service based spring boot architecture where we are using spring webclient (which internally uses reactor netty) for internal communication between services. The issue that we faced...

github.com

 

 

해결방법

일단 해결 방법은 B서버 로드밸런서의 타임아웃시간에 도달하기 전에 우리쪽에서 먼저 연결을 해제하고

다시 커넥트를 거는것으로 해결 하였고 

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
@Configuration
public class HttpProxyConfiguration {
 
    @Value("${tracker.url}")
    private String trackerUrl;
 
    @Bean
    TrackerClient trackerClient(WebClient.Builder builder) {
        ConnectionProvider provider = ConnectionProvider.builder("fixed")
                .maxConnections(500)
                .maxIdleTime(Duration.ofSeconds(20))
                .maxLifeTime(Duration.ofSeconds(60))
                .pendingAcquireTimeout(Duration.ofSeconds(60))
                .evictInBackground(Duration.ofSeconds(120)).build();
 
        HttpClient httpClient = HttpClient.create(provider);
        httpClient.warmup().block();
 
        var reactorClientHttpConnector = new ReactorClientHttpConnector(httpClient);
 
        var wc = builder.baseUrl(trackerUrl)
                .clientConnector(reactorClientHttpConnector)
                .build();
 
        var wca = WebClientAdapter.forClient(wc);
        return HttpServiceProxyFactory.builder()
                .clientAdapter(wca)
                .build()
                .createClient(TrackerClient.class);
    }
}
cs

 

이 코드를 참조하여 문제를 수정한것을 확인 할 수 있었고

위 코드의 각 부분의 설명을 보자면

  1. @Value("${tracker.url}"): 이 어노테이션은 tracker.url이라는 이름의 프로퍼티 값을 trackerUrl 변수에 주입(inject)합니다. 이 프로퍼티 값은 외부 트래커 서비스의 기본 URL을 포함하며, 애플리케이션 구성 파일(예: application.properties 또는 application.yml)에서 정의됩니다.
  2. TrackerClient trackerClient(WebClient.Builder builder) 메서드: 이 메서드는 TrackerClient 인터페이스의 구현체를 생성하고 구성합니다. 이 인터페이스는 외부 트래커 서비스와의 통신을 위한 메서드를 정의합니다.
  3. ConnectionProvider 설정: ConnectionProvider는 WebClient의 네트워크 연결을 관리하는 데 사용됩니다. 여기서는 최대 연결 수, 최대 유휴 시간, 연결의 최대 수명, 대기 중인 연결 획득 타임아웃 등을 설정합니다. 이 설정은 서비스와의 통신 중 발생할 수 있는 다양한 시나리오를 관리하기 위한 것입니다.
  4. HttpClient 생성 및 설정: HttpClient.create(provider)를 사용하여 ConnectionProvider를 사용하는 HttpClient 인스턴스를 생성합니다. httpClient.warmup().block() 호출은 HttpClient를 "온기"시키며, 네트워크 연결을 미리 설정하여 첫 번째 요청의 지연 시간을 줄이는 데 도움이 됩니다.
  5. WebClient 구성 및 생성: WebClient.Builder 인스턴스에 기본 URL, 클라이언트 커넥터 등을 설정하여 WebClient 인스턴스를 생성합니다. 이 WebClient 인스턴스는 TrackerClient의 HTTP 요청을 실행하는 데 사용됩니다.
  6. HttpServiceProxyFactory를 사용한 TrackerClient 생성: 마지막으로, HttpServiceProxyFactory와 WebClientAdapter를 사용하여 WebClient를 기반으로 하는 TrackerClient 인스턴스를 생성합니다. 이를 통해 외부 트래커 서비스와의 통신을 위한 프록시 클라이언트를 얻게 됩니다.

 

이렇게 정리될수 있으며 ConnectionProvider의 옵션은

  • builder("fixed"): 연결 풀의 이름을 "fixed"로 설정합니다. 이 이름은 로깅이나 디버깅 시 해당 연결 풀을 식별하는 데 사용될 수 있습니다.
  • maxConnections(500): 연결 풀이 동시에 유지할 수 있는 최대 연결 수를 500개로 설정합니다. 이는 애플리케이션이 동시에 열 수 있는 최대 연결 수를 의미하며, 이 한도를 초과하는 연결 요청은 대기 상태가 될 수 있습니다.
  • maxIdleTime(Duration.ofSeconds(20)): 연결이 유휴 상태(즉, 데이터를 전송하지 않는 상태)로 있을 수 있는 최대 시간을 20초로 설정합니다. 유휴 시간이 이 값을 초과하면 연결이 자동으로 종료됩니다.
  • maxLifeTime(Duration.ofSeconds(60)): 연결이 생성된 후 유지될 수 있는 최대 시간을 60초로 설정합니다. 이 시간이 지나면, 연결은 사용 여부에 관계없이 종료됩니다.
  • pendingAcquireTimeout(Duration.ofSeconds(60)): 연결을 획득하기 위해 대기하는 최대 시간을 60초로 설정합니다. 연결 풀에서 사용 가능한 연결을 얻기 위해 이 시간을 초과하여 대기하는 요청은 실패하게 됩니다.
  • evictInBackground(Duration.ofSeconds(120)): 비활성 연결을 정리하는 배경 작업의 실행 간격을 120초로 설정합니다. 이 설정은 연결 풀에서 오래되거나 더 이상 필요하지 않은 연결을 주기적으로 제거하는 데 사용됩니다.

위와 같다.

기존코드는 위의 옵션에 대한 별도의 설정이 없었기에 해당 오류가 발생하였었다.

+ Recent posts