본문 바로가기

개발/API

HTTP에서는 끊기는데 HTTPS에서는 정상이다... 같은 API인데 왜 다를까?

기존에 정상적으로 사용하던 지도 API가 갑자기 제대로 호출되지 않는다는 문의가 들어왔다.

지도뿐만 아니라 공간정보를 조회하는 WFS API도 정상적으로 동작하지 않는다고 했다.

처음에는 API 서버에 장애가 생긴 줄 알았다.

그런데 직접 호출해보니 조금 이상했다.

아예 접속이 안 되는 것도 아니었고, 서버에서는 200 OK 응답도 내려왔다.

데이터도 실제로 내려오기 시작했다.

문제는 잘 받아오다가 중간에 갑자기 연결이 끊긴다는 것이었다.

요청
 ↓
HTTP 200 OK
 ↓
데이터 수신 시작
 ↓
데이터 일부 수신
 ↓
연결 종료
 

처음에는 데이터가 너무 커서 그런가 싶었다.

WFS는 이전에도 대용량 데이터를 한 번에 조회하면서 브라우저 메모리를 엄청나게 사용했던 경험이 있었기 때문이다.

그런데 이번에는 조금 다른 문제였다.


브라우저 문제인지 먼저 확인해봤다

웹 화면에서는 JavaScript 파일이나 지도 관련 API를 호출하는 과정에서 다음과 같은 현상이 나타났다.

ERR_CONNECTION_RESET
timeout
 

필요한 JavaScript 파일을 정상적으로 불러오지 못하니 이후 코드에서는 정의되어 있어야 할 객체가 없다는 오류까지 이어졌다.

 
Uncaught ReferenceError: SomeMapLayer is not defined
 

처음에는 JavaScript 문제처럼 보일 수도 있다.

하지만 앞에서 필요한 파일 자체를 정상적으로 받아오지 못했다면 뒤에서 발생하는 ReferenceError는 원인이라기보다 앞선 요청 실패의 결과일 가능성이 높다.

그래서 브라우저에서만 확인하지 않고 서버에서 직접 API를 호출해보기로 했다.


curl로 직접 호출해봤다

서버에서 curl을 이용해 같은 API를 호출했다.

 
curl -v "http://example.com/api/example"
 

그런데 여기에서도 비슷했다.

응답 헤더 자체는 정상적으로 내려왔다.

HTTP/1.1 200 OK
Content-Type: application/xml
 

그리고 XML 데이터도 실제로 출력되기 시작했다.

그런데 끝까지 내려오지 않고 중간에서 연결이 종료됐다.

이걸 보고 브라우저 JavaScript만의 문제는 아니라는 생각이 들었다.

브라우저에서 실패
        +
서버 curl에서도 실패
        ↓
프론트엔드 코드만의 문제는 아닐 가능성
 

200 OK인데 실패할 수도 있나?

처음에는 이 부분이 조금 헷갈렸다.

200 OK가 내려왔으면 서버가 정상적으로 응답했다는 뜻 아닌가?

그런데 200 OK는 HTTP 응답의 상태를 나타내는 값이지, 클라이언트가 응답 본문 전체를 끝까지 정상적으로 받았다는 것을 보장하는 값은 아니다.

예를 들어 서버가 응답을 시작하면서 먼저

 
HTTP/1.1 200 OK
 

를 전달하고 이후 응답 본문을 전송한다고 생각해보면 된다.

HTTP/1.1 200 OK

<xml>
    데이터
    데이터
    데이터
    ...
 

헤더를 받은 뒤 데이터를 전송하는 과정에서 연결이 끊긴다면 클라이언트에서는 200을 확인했지만 완전한 응답은 받지 못한 상태가 될 수 있다.

이번 현상이 딱 그런 모습이었다.


혹시 HTTPS로 호출하면?

마침 해당 서비스는 HTTP에서 HTTPS로 전환하는 작업도 진행하고 있었다.

그래서 같은 요청을 HTTPS로도 호출해봤다.

 
curl -v "https://example.com/api/example"
 

그런데 결과가 달랐다.

HTTPS에서는 데이터가 끝까지 정상적으로 내려왔다.

정리하면 이랬다.

호출 방식결과
HTTP 200 응답 후 데이터 수신 중 연결 종료
HTTPS 정상적으로 끝까지 수신

여기서부터 데이터나 API 로직 자체보다는 HTTP와 HTTPS 요청이 지나가는 경로의 차이를 의심하게 됐다.

HTTP로 호출 (실패)

 

HTTPS로 호출 (성공)


같은 API인데 왜 결과가 다를까?

HTTP와 HTTPS는 같은 애플리케이션으로 들어가더라도 항상 완전히 같은 경로를 거친다고 볼 수는 없다.

서버 구성에 따라 중간에

클라이언트
   ↓
방화벽 / 보안장비
   ↓
L4 / Load Balancer
   ↓
Web Server
   ↓
WAS
 

같은 여러 구간을 지나갈 수 있다.

HTTP와 HTTPS가 서로 다른 포트나 리스너, 보안 정책을 사용한다면 중간 장비에서 서로 다르게 처리될 수도 있다.

예를 들어 일반적으로 HTTP는 80, HTTPS는 443을 사용하는데 특정 정책이 HTTP 요청에만 적용되어 있다면

HTTP
Client → 80 → 특정 정책 → Server

HTTPS
Client → 443 → 다른 정책 → Server
 

처럼 실제 통신 경로에 차이가 생길 수 있다.

그래서 “같은 URL인데 http만 https로 바꿨을 뿐”이라고 생각했던 것과 달리 서버 입장에서는 완전히 동일한 요청 경로가 아닐 수도 있었다.


Spring Security 설정도 확인했다

애플리케이션에서는 Spring Security의 requires-channel 설정도 사용하고 있었다.

예를 들어 다음과 같은 설정이다.

 
<intercept-url
    pattern="/api/**"
    access="permitAll"
    requires-channel="https" />
 

requires-channel="https"는 해당 요청에 HTTPS 채널을 요구하는 설정이다.

반대로 HTTP와 HTTPS를 모두 허용해야 하는 경로라면 구성에 따라 다음처럼 설정할 수도 있다.

 
<intercept-url
    pattern="/api/**"
    access="permitAll"
    requires-channel="any" />
 

다만 이번처럼 200 응답을 받은 뒤 데이터 전송 중 연결이 끊기는 현상을 이 설정 하나만으로 설명할 수는 없었다.

Spring Security뿐만 아니라 앞단의 웹서버, L4, 방화벽 또는 보안 정책 등 HTTP 요청 경로 전체를 같이 확인해야 했다.


다른 서버에서는 정상이라는 것도 단서였다

또 하나 이상했던 점이 있었다.

같은 API를 호출했는데 모든 서버에서 동일하게 문제가 발생하는 것도 아니었다.

서버 A → HTTP 호출 중간 종료
서버 B → 정상
 

이런 차이가 있다면 API 자체가 항상 잘못된 응답을 주고 있다고 보기도 어렵다.

특정 서버의 네트워크 경로나 적용된 보안 정책 등 환경 차이도 확인할 필요가 있다.

이런 문제에서는 단순히 애플리케이션 로그만 계속 보는 것보다

브라우저에서는?
서버 curl에서는?
HTTP에서는?
HTTPS에서는?
다른 서버에서는?
 

처럼 조건을 하나씩 바꿔가면서 비교하는 게 원인을 좁히는 데 더 도움이 됐다.

HTTP와 HTTPS 요청 경로 예시


200 OK만 보고 정상이라고 판단하면 안 됐다

이번에 가장 헷갈렸던 건 역시 200 OK였다.

처음에는 200이 찍히니까 서버에서는 정상적으로 응답한 것처럼 보였다.

하지만 실제로 중요한 건 응답 코드만이 아니라 데이터가 끝까지 정상적으로 전달됐는지였다.

특히 대용량 데이터나 스트리밍 형태의 응답이라면

HTTP Status
 

만 확인하는 것보다

응답 크기
전송 완료 여부
연결 종료 시점
curl 결과
HTTP / HTTPS 차이
다른 서버에서의 결과
 

까지 같이 확인하는 게 좋다.

이번에는 정확히 어느 장비나 정책이 HTTP 연결을 종료시켰는지까지 확정한 것은 아니지만,

API 자체의 데이터 문제 → 브라우저 문제 → 서버 직접 호출 → HTTP/HTTPS 비교

순서로 확인하면서 적어도 문제 범위를 HTTP 통신 경로 쪽으로 상당히 좁힐 수 있었다.

처음에는 지도 API랑 WFS가 동시에 이상해서 서비스 전체가 장애 난 줄 알았는데,

HTTP는 중간에 끊기고 HTTPS는 멀쩡한 걸 확인하고 나니 봐야 할 곳이 완전히 달라졌다.