Go HTTP 클라이언트는 transport에서 프록시를 선택합니다. 표준 transport를 수정하기 전에 복제하면 기본 클라이언트를 쓰는 다른 코드는 기존 경로를 유지할 수 있습니다.

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "net/url"
    "time"
)

func main() {
    proxyURL, err := url.Parse("http://203.0.113.10:8080")
    if err != nil {
        log.Fatal(err)
    }

    transport := http.DefaultTransport.(*http.Transport).Clone()
    transport.Proxy = http.ProxyURL(proxyURL)
    defer transport.CloseIdleConnections()

    client := &http.Client{
        Transport: transport,
        Timeout:   15 * time.Second,
    }

    response, err := client.Get("https://example.com/")
    if err != nil {
        log.Fatal(err)
    }
    defer response.Body.Close()

    fmt.Println(response.StatusCode)
    if _, err := io.Copy(io.Discard, response.Body); err != nil {
        log.Fatal(err)
    }
}

이 주소는 문서용 예시입니다. HTTP 프록시 목록에서 고른 후보처럼 실제 HTTP 프록시 IP와 포트로 바꾸세요. 예제는 Go 표준 라이브러리만 사용합니다. 이 HTTP 프록시 요청에는 SOCKS 패키지나 프로세스 전체에 적용되는 환경 변수가 필요 없습니다.

DefaultTransport를 직접 바꾸지 않는 이유

기본 transport는 공유됩니다. 이를 수정하면 해당 프록시 사용을 요청하지 않은 코드의 동작까지 달라질 수 있고, 요청이 사용하는 중에 변경하면 피할 수 있는 동시성 문제도 생깁니다. 전용 transport를 두면 경로를 이 클라이언트의 설정으로 한정할 수 있습니다.

http.ProxyURL은 전달한 URL을 일관되게 선택하는 선택기를 반환합니다. 프록시 변수와 우회 규칙을 고려하는 기본 환경 선택기와 다릅니다. HTTP 목록의 특정 항목 하나를 테스트하려는 목적이라면 명시적 선택기로 이 모호함을 없앨 수 있습니다.

응답을 닫고 클라이언트 재사용하기

예제는 응답 본문을 끝까지 읽고 닫습니다. 끝까지 읽으면 transport가 가능한 경우 연결을 재사용하는 데 도움이 됩니다. 같은 프록시로 여러 요청을 보내는 프로그램이라면 매번 새로 만들지 말고 클라이언트와 transport를 유지하세요.

클라이언트 제한 시간은 소켓을 여는 시간뿐 아니라 응답 본문 읽기를 포함한 요청 수명 전체를 제한합니다. Get이 반환된 뒤의 읽기 오류도 처리해야 합니다. 403 같은 상태는 Go transport 오류가 아니므로 StatusCode와 err를 따로 확인하세요.

관련 글