빌드노트
curl로 127.0.0.1을 불렀는데 프록시 응답이 온다
curl의 로컬 요청도 proxy 환경에 따라 프록시로 갈 수 있다. 새 loopback 수신기로 기본 요청과 --noproxy의 접속 포트·응답을 비교하고, localhost 예외와 숫자 주소의 차이 및 HTTP/HTTPS 환경 변수 대소문자 규칙을 확인한다.
터미널에서 curl로 로컬 서버를 불렀는데 프록시 안내 페이지나 예상과 다른 응답이 올 수 있다. URL이 127.0.0.1이어도 curl이 반드시 그 주소에 직접 연결하는 것은 아니다. http_proxy 같은 환경 변수로 프록시가 선택됐고 해당 호스트가 제외 목록에 없으면, 로컬 URL도 프록시로 보낸다. 같은 URL을 기본 요청과 `--noproxy` 요청으로 비교해 실제 연결 대상이 바뀌는지 먼저 확인한다.
응답 본문과 연결 대상을 함께 본다
다음은 Windows의 PowerShell 7에서 외부 실행 파일 curl.exe를 쓰는 명령이다. 먼저 문제가 난 명령을 실행한 것과 같은 터미널에서 관련 환경 변수의 이름과 값 존재 여부만 확인한다. 프록시 URL 전체를 출력하지 않으므로 URL에 들어 있는 사용자 이름이나 비밀번호도 이 조회에는 나오지 않는다.
Get-ChildItem Env: |
Where-Object Name -Match '^(https?_proxy|all_proxy|no_proxy)$' |
Select-Object Name, @{Name='HasValue'; Expression={$_.Value.Length -gt 0}}
이 목록은 설정의 존재만 보여 준다. 실제 선택된 변수와 접속 주소는 다음 요청의 -v 출력에서 읽는다. IDE에서 실행하거나 별도 작업 프로세스가 호출한 curl이라면 그 프로세스가 물려받은 환경을 확인해야 한다. 다른 터미널에서 같은 이름의 변수가 보인다는 것만으로는 충분하지 않다.
자신이 띄운 로컬 HTTP 서버의 읽기 전용 경로를 대상으로 두 요청을 보낸다. 아래의 8080/health를 실제 포트와 경로로 바꾸고, 두 요청 모두 같은 URL을 유지한다. -v는 주소와 헤더를 출력하므로 인증 정보가 없는 로컬 GET으로 비교하고 로그를 공유할 때도 내용을 확인한다.
$url = 'http://127.0.0.1:8080/health'
curl.exe -q --ipv4 -sS -v --connect-timeout 1 --max-time 3 $url
curl.exe -q --ipv4 -sS -v --connect-timeout 1 --max-time 3 --noproxy '127.0.0.1' $url
-q를 첫 옵션으로 두어 기본 curl 설정 파일을 읽지 않게 했다. 환경 변수는 그대로 적용된다. --ipv4는 이 IPv4 비교의 조건이며, 시간 제한은 응답을 무한히 기다리지 않기 위한 것이다. Bash에서는 실행 파일 이름을 curl로 바꿔 같은 요청 옵션을 쓸 수 있다. curl 설정 파일 옵션 (새 탭에서 열림)
두 수신기를 새로 띄워 이 차이를 실제로 확인했다. 원점은 127.0.0.1:57715, 합성 프록시는 127.0.0.1:57716에만 열었다. 자식 curl에만 http_proxy=http://127.0.0.1:57716을 주고 제외 목록은 주지 않았다. 합성 프록시는 원점으로 전달하지 않고 SYNTHETIC_PROXY를 반환하며, 원점은 ORIGIN을 반환하도록 만든 별도 실험이다.
Windows curl 8.21.0에서 기본 요청은 다음 줄을 남겼다. 관련 줄만 발췌했다.
* Uses proxy env variable http_proxy == 'http://127.0.0.1:57716'
* Trying 127.0.0.1:57716...
> GET http://127.0.0.1:57715/health HTTP/1.1
SYNTHETIC_PROXY
같은 URL에 --noproxy '127.0.0.1'을 붙이자 다음처럼 바뀌었다.
* Trying 127.0.0.1:57715...
> GET /health HTTP/1.1
ORIGIN
기본 요청은 합성 프록시만 받았고, 두 번째 요청은 원점만 받았다. 두 응답 모두 HTTP 200이고 curl 종료 코드도 0이었다. 따라서 이 비교에서 성공 여부나 응답 본문만 봤다면 서로 다른 수신기로 간 사실을 놓칠 수 있었다. 특히 두 수신기 모두 IP가 127.0.0.1이므로 접속 포트까지 비교해야 한다. WSL Linux의 curl 8.5.0에서도 새 포트로 같은 수신 대상 변화를 확인했다.
localhost 예외가 숫자 주소까지 포함하지는 않는다
동일한 실험에서 http_proxy는 유지하고 자식 프로세스의 제외 목록만 바꿨다. URL의 호스트는 계속 127.0.0.1이었다.
no_proxy=localhost만 있으면 합성 프록시가 요청을 받았다.no_proxy=127.0.0.1이면 원점이 직접 받았다.NO_PROXY=127.0.0.1도 두 환경에서 직접 연결됐다.
이 결과에서 localhost를 “모든 로컬 주소”라는 뜻으로 해석할 수는 없다. 제외할 때는 URL에 실제로 쓴 호스트를 확인한다. 목록에는 http://나 /health를 넣지 않고 호스트를 적으며, 여러 개는 쉼표로 구분한다. 프록시 환경 변수와 제외 목록 (새 탭에서 열림)
한 번의 직접 연결을 확인하려면 위처럼 해당 호스트에만 --noproxy를 적용하면 된다. 이 옵션은 환경의 no_proxy·NO_PROXY 목록을 대체한다. 실제로 환경에 no_proxy=127.0.0.1이 있어도 옵션을 --noproxy localhost로 주자 다시 합성 프록시로 갔다. 기존 목록에 추가되는 것으로 생각하면 안 된다. curl의 --noproxy 규칙 (새 탭에서 열림)
직접 연결에서만 기대한 응답이 나오면 해당 로컬 목적지를 제외 목록에 넣는 것이 맞는지 판단할 근거가 생긴다. 반복 작업에서는 그 작업 프로세스에 필요한 예외를 전달하되 기존에 필요한 항목도 보존한다. 두 요청의 접속 대상이 이미 같다면 이 환경 변수 가설로는 차이를 설명하지 못한다. 직접 연결이 거부되면 우선 그 주소와 포트에서 서버가 듣는지 확인한다. 프록시를 거쳐야 하는 목적지까지 제외할 근거는 아니다.
HTTP_PROXY와 HTTPS_PROXY는 같은 대소문자 규칙이 아니다
환경을 옮겨 진단할 때는 다음 계약을 구분해야 한다. curl 공식 환경 변수 설명 (새 탭에서 열림)은 http_proxy의 소문자 예외와 Windows의 대소문자 비구분을 함께 명시한다.
- HTTP URL의 프록시 변수는
http_proxy다. 대소문자를 구분하는 환경에서는 대문자HTTP_PROXY를 같은 설정으로 취급하지 않는다. - HTTPS URL에는
https_proxy와HTTPS_PROXY를 쓸 수 있다. 변수 이름의 HTTPS는 목적지 URL의 스킴이며, 그 값이 반드시https://로 시작해야 한다는 뜻은 아니다. - 해당 스킴의 변수가 없으면
all_proxy·ALL_PROXY도 확인한다. 프록시 변수 하나만 지웠다고 직접 연결이 된다고 단정하지 않는다.
직접 실행한 Linux curl 8.5.0에서는 HTTP_PROXY만 줬을 때 원점으로 갔다. 반면 Windows curl 8.21.0에서는 같은 대문자 이름으로도 합성 프록시에 갔다. Windows에서 이름의 대소문자만 바꿔 별도 설정을 만들 수 있다고 생각하면 이 차이를 놓친다.
HTTPS 변수는 두 환경 모두 소문자와 대문자 각각에 http:// 합성 프록시 주소를 넣어 확인했다. 수신기는 두 경우 모두 CONNECT 요청을 받았다. 이 실험은 터널을 의도적으로 502로 거부했으므로 HTTPS 응답이나 TLS 인증서 검증의 성공을 뜻하지 않는다. 실제 회사 프록시의 전달 동작, IPv6 연결, 다른 네트워크 도구의 환경 변수 규칙도 검증하지 않았다.