VB.NET에서 HTTP 요청을 보낼 때는 보통 HttpClient나 WebClient를 쓰지만, VBA에서 쓰던 코드를 옮겨 오거나 쿠키·리다이렉트를 세밀하게 제어해야 할 때는 WinHttp를 그대로 가져다 쓰는 경우가 많습니다. 참조 추가에서 COM의 "Microsoft WinHTTP Services, version 5.1"을 넣으면 Interop.WinHttp 어셈블리가 생기고, 그 안의 WinHttpRequest를 쓸 수 있습니다. 개체 브라우저에서 정의로 이동하면 나오는 선언을 옮겨 두고, 자주 쓰는 멤버를 정리했습니다.

인터페이스 선언 (Interop.WinHttp, Version=5.1.0.0)

<Guid("016FE2EC-B2C8-45F8-B23B-39E53A75396B")> <TypeLibTypeAttribute(4288)>
Public Interface IWinHttpRequest
    <DispId(6)>
    Property [Option]([Option] As WinHttpRequestOption) As Object
    <DispId(10)>
    ReadOnly Property ResponseBody As Object
    <DispId(9)>
    ReadOnly Property ResponseText As String
    <DispId(8)>
    ReadOnly Property StatusText As String
    <DispId(7)>
    ReadOnly Property Status As Integer
    <DispId(11)>
    ReadOnly Property ResponseStream As Object
    <DispId(18)>
    Sub SetAutoLogonPolicy(AutoLogonPolicy As WinHttpRequestAutoLogonPolicy)
    <DispId(17)>
    Sub SetClientCertificate(ClientCertificate As String)
    <DispId(16)>
    Sub SetTimeouts(ResolveTimeout As Integer, ConnectTimeout As Integer, SendTimeout As Integer, ReceiveTimeout As Integer)
    <DispId(12)>
    Sub Abort()
    <DispId(5)>
    Sub Send(Optional Body As Object = Nothing)
    <DispId(2)>
    Sub SetRequestHeader(Header As String, Value As String)
    <DispId(1)>
    Sub Open(Method As String, Url As String, Optional Async As Object = Nothing)
    <DispId(14)>
    Sub SetCredentials(UserName As String, Password As String, <ComAliasName("WinHttp.HTTPREQUEST_SETCREDENTIALS_FLAGS")> Flags As Integer)
    <DispId(13)>
    Sub SetProxy(<ComAliasName("WinHttp.HTTPREQUEST_PROXY_SETTING")> ProxySetting As Integer, Optional ProxyServer As Object = Nothing, Optional BypassList As Object = Nothing)
    <DispId(4)>
    Function GetAllResponseHeaders() As String
    <DispId(3)>
    Function GetResponseHeader(Header As String) As String
    <DispId(15)>
    Function WaitForResponse(Optional Timeout As Object = Nothing) As Boolean
End Interface

Option 속성에 넘기는 값

[Option] 속성은 아래 열거형 값을 키로 받아 여러 동작을 켜고 끕니다. VB에서 Option이 예약어라 대괄호로 감싸 쓰는 점이 처음에 낯섭니다.

광고
<Guid("12782009-FE90-4877-9730-E5E183669B19")>
Public Enum WinHttpRequestOption
    WinHttpRequestOption_UserAgentString = 0
    WinHttpRequestOption_URL = 1
    WinHttpRequestOption_URLCodePage = 2
    WinHttpRequestOption_EscapePercentInURL = 3
    WinHttpRequestOption_SslErrorIgnoreFlags = 4
    WinHttpRequestOption_SelectCertificate = 5
    WinHttpRequestOption_EnableRedirects = 6
    WinHttpRequestOption_UrlEscapeDisable = 7
    WinHttpRequestOption_UrlEscapeDisableQuery = 8
    WinHttpRequestOption_SecureProtocols = 9
    WinHttpRequestOption_EnableTracing = 10
    WinHttpRequestOption_RevertImpersonationOverSsl = 11
    WinHttpRequestOption_EnableHttpsToHttpRedirects = 12
    WinHttpRequestOption_EnablePassportAuthentication = 13
    WinHttpRequestOption_MaxAutomaticRedirects = 14
    WinHttpRequestOption_MaxResponseHeaderSize = 15
    WinHttpRequestOption_MaxResponseDrainSize = 16
    WinHttpRequestOption_EnableHttp1_1 = 17
    WinHttpRequestOption_EnableCertificateRevocationCheck = 18
    WinHttpRequestOption_RejectUserpwd = 19
End Enum

제가 가장 자주 만지는 건 EnableRedirects(6)입니다. 로그인 후 302로 넘어가는 응답에서 쿠키를 가로채야 할 때 이 값을 False로 꺼 두고 GetResponseHeader("Location")이나 Set-Cookie를 직접 읽습니다. UserAgentString(0)은 서버가 브라우저가 아니라고 응답을 거부할 때 바꿔 주고, SslErrorIgnoreFlags(4)는 사내 테스트 서버처럼 인증서가 엉성한 곳에 붙을 때 씁니다.

메서드별로 알아 둘 점

멤버 하는 일
Open 메서드(GET/POST 등)와 URL을 정합니다. 세 번째 인자에 True를 주면 비동기 모드
SetTimeouts 이름 해석·연결·전송·수신 시간 제한을 차례로 지정. 네 값 모두 밀리초 단위
SetRequestHeader 요청 헤더 이름과 값을 지정. Open 뒤, Send 앞에 호출
Send 실제로 요청을 보냅니다. 인자는 선택이지만 POST라면 본문을 넣어야 함
WaitForResponse 비동기로 열었을 때 응답을 다 받을 때까지 기다림
SetProxy 통신에 쓸 프록시 지정: (설정 값, "주소:포트", 우회 목록)
ResponseText 받은 응답을 문자열로 가져옴
ResponseBody 받은 응답을 바이트 배열로 가져옴
Abort 진행 중인 요청을 중단
GetResponseHeader 지정한 응답 헤더 하나를 가져옴. 해당 헤더가 없으면 오류 발생
GetAllResponseHeaders 응답 헤더 전체를 문자열로 가져옴

SetTimeouts는 예를 들어 WinHttp.SetTimeouts(3000, 3000, 3000, 3000)처럼 씁니다. 순서가 꼭 Open 전이어야 하는 건 아니지만 Send 전에는 불러야 적용됩니다. 기본 수신 제한이 꽤 길어서, 응답 없는 서버에 붙으면 프로그램이 한참 멈춘 것처럼 보이니 명시적으로 줄여 두는 게 좋습니다.

GetResponseHeader가 헤더가 없을 때 빈 문자열이 아니라 예외를 던진다는 점은 꼭 기억해 두세요. 쿠키나 Location처럼 있을 수도 없을 수도 있는 헤더는 Try로 감싸거나, GetAllResponseHeaders로 전체를 받아서 문자열에서 찾는 편이 안전합니다.

SetProxy의 첫 번째 인자 값은 다음과 같습니다.

HTTPREQUEST_PROXYSETTING_DEFAULT = 0
HTTPREQUEST_PROXYSETTING_PRECONFIG = 0
HTTPREQUEST_PROXYSETTING_DIRECT = 1
HTTPREQUEST_PROXYSETTING_PROXY = 2

DEFAULT와 PRECONFIG는 같은 값(0)입니다. 프록시를 직접 지정하려면 2를 주고 두 번째 인자에 "주소:포트"를 넣으면 됩니다. Fiddler 같은 도구로 요청을 들여다볼 때 127.0.0.1:8888을 넣어 두면 디버깅이 편합니다.

간단한 사용 예

Dim req As New WinHttp.WinHttpRequest
req.Open("GET", "https://example.com/", False)
req.SetTimeouts(3000, 3000, 3000, 3000)
req.SetRequestHeader("User-Agent", "Mozilla/5.0")
req.Send()

If req.Status = 200 Then
    Console.WriteLine(req.ResponseText)
Else
    Console.WriteLine(req.Status & " " & req.StatusText)
End If

ResponseText는 응답 헤더의 charset을 참고해 문자열로 바꿔 주는데, 헤더에 charset이 없는 EUC-KR 페이지는 한글이 깨져 나옵니다. 그럴 때는 ResponseBody로 바이트를 받아 Encoding.GetEncoding(51949)처럼 직접 디코딩하면 됩니다(.NET Core 이상이라면 인코딩 공급자 등록이 먼저 필요합니다).

이벤트 받기

비동기로 진행 상황을 받고 싶다면 이벤트를 쓸 수 있습니다. 단, 이벤트 프로시저를 쓰려면 변수가 WithEvents로 선언되어 있어야 합니다.

Private Sub WinHttp_OnError(ByVal ErrorNumber As Long, ByVal ErrorDescription As String)
    ' Winhttp 통신 중 에러가 발생할 경우 발생합니다.
    ' ErrorNumber : 에러 번호, ErrorDescription : 에러 String
End Sub

Private Sub WinHttp_OnResponseDataAvailable(Data() As Byte)
    ' 데이터를 받을 때 발생합니다.
    ' Data() : 받은 데이터
End Sub

Private Sub WinHttp_OnResponseFinished()
    ' 통신이 끝날 경우 발생합니다.
End Sub

Private Sub WinHttp_OnResponseStart(ByVal Status As Long, ByVal ContentType As String)
    ' 통신이 시작될 경우 발생합니다.
    ' Status : 상태, ContentType : 콘텐츠 타입
End Sub

위 시그니처는 VBA·VB6 스타일(WinHttp_이벤트명 이름 규칙)입니다. VB.NET에서는 이름이 아니라 Handles WinHttp.OnResponseFinished처럼 Handles 절로 연결해야 이벤트가 들어오고, 이벤트는 UI 스레드가 아닌 곳에서 올라올 수 있으니 폼 컨트롤을 건드릴 때는 Invoke를 거치는 편이 안전합니다. 대량 파일을 받으면서 진행률을 보여 주는 정도가 아니라면, 동기 모드로 Send 한 번 하고 결과를 읽는 쪽이 코드도 짧고 디버깅도 쉽습니다.