HTTP-запросы из VB6 через MSXML2.XMLHTTP и ServerXMLHTTP: GET, POST, загрузка файлов

РазделVisual Basic 6УровеньСредний Чтение8 минОпубликовано ТегиVB6HTTPMSXMLXMLHTTP

Компонент MSXML превращает VB6 в простой HTTP-клиент: можно скачать страницу, вызвать API или отправить форму. Разберём рабочие примеры и грабли, на которые наступает почти каждый.

Главное. Для HTTP-запросов из VB6 берите MSXML2.ServerXMLHTTP.6.0 с тайм-аутами и всегда проверяйте Status. Двоичные данные и ответы в нестандартной кодировке читайте через responseBody, а проблемы с HTTPS ищите в поддержке TLS 1.2 на уровне Windows.

Какой объект выбрать

В составе MSXML есть два похожих класса для HTTP-запросов, и разница между ними важнее, чем кажется. XMLHTTP построен на WinINet, той же библиотеке, что использовал Internet Explorer. Он берёт настройки прокси и безопасности из браузера, кэширует ответы и может показывать диалоги авторизации. Для настольной программы, которая работает от имени пользователя, это бывает удобно.

ServerXMLHTTP построен на WinHTTP. Он не зависит от настроек браузера, не показывает окон, не кэширует и позволяет задать тайм-ауты. Это правильный выбор для фоновых задач, служб и обращений к API.

Указывайте версию явно: MSXML2.ServerXMLHTTP.6.0 или MSXML2.XMLHTTP.6.0. MSXML 6 входит в Windows начиная с XP SP3, а безверсионный ProgID может привести к устаревшей и менее безопасной версии 3.0. Подключать ссылку на Microsoft XML не обязательно, с поздним связыванием код переносимее.

GET-запрос с проверкой статуса

Третий параметр метода Open задаёт режим: False означает синхронный запрос, при котором send не возвращает управление, пока не придёт ответ. Самая частая ошибка в простых примерах: отсутствие проверки статуса. Сервер может вернуть страницу 404 или 500, и программа примет текст ошибки за полезные данные.

Метод setTimeouts принимает четыре значения в миллисекундах: на разрешение имени, подключение, отправку и получение. Без него зависший сервер может заморозить программу на минуты.

modHttp.basVB6
Public Function HttpGet(ByVal strUrl As String) As String
    Dim http As Object
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    http.setTimeouts 5000, 5000, 10000, 30000   ' resolve, connect, send, receive
    http.Open "GET", strUrl, False              ' False = синхронный запрос
    http.setRequestHeader "User-Agent", "MyVB6App/1.0"
    http.send
    If http.Status <> 200 Then
        Err.Raise vbObjectError + 513, "HttpGet", _
                  "HTTP " & http.Status & " " & http.statusText
    End If
    HttpGet = http.responseText
End Function

' Пример: MsgBox HttpGet("https://example.com/api/version")

POST-запрос и кодирование параметров

Для отправки формы тело передаётся аргументом метода send, а заголовок Content-Type сообщает серверу формат. Значения полей обязательно кодируются: пробел, амперсанд или кириллица без кодирования испортят запрос. Встроенной функции UrlEncode в VB6 нет, поэтому пишем свою: получаем байты UTF-8 через ADODB.Stream и заменяем всё, кроме безопасных символов, на последовательности %XX.

Тело запроса затем собирается так: "name=" & UrlEncodeUtf8(txtName) & "&city=" & UrlEncodeUtf8(txtCity). Для JSON-API вместо этого передаётся строка JSON с заголовком application/json.

modHttp.basVB6
Public Function UrlEncodeUtf8(ByVal s As String) As String
    Dim stm As Object, b() As Byte, i As Long, out As String
    If Len(s) = 0 Then Exit Function
    Set stm = CreateObject("ADODB.Stream")
    stm.Type = 2: stm.Charset = "utf-8": stm.Open
    stm.WriteText s
    stm.Position = 0: stm.Type = 1               ' переключаемся на байты
    stm.Position = 3                             ' пропускаем BOM EF BB BF
    b = stm.Read
    stm.Close
    For i = 0 To UBound(b)
        Select Case b(i)
            Case 48 To 57, 65 To 90, 97 To 122, 45, 46, 95, 126  ' 0-9 A-Z a-z - . _ ~
                out = out & Chr$(b(i))
            Case Else
                out = out & "%" & Right$("0" & Hex$(b(i)), 2)
        End Select
    Next
    UrlEncodeUtf8 = out
End Function

Public Function HttpPostForm(ByVal strUrl As String, ByVal strBody As String) As String
    Dim http As Object
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    http.Open "POST", strUrl, False
    http.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    http.send strBody
    HttpPostForm = http.responseText
End Function

Кодировка ответа и скачивание файлов

Свойство responseText декодирует ответ по кодировке из заголовка Content-Type, а если её там нет, считает текст UTF-8. Старые сайты в windows-1251 часто не указывают charset, и вместо кириллицы получаются знаки вопроса. В таком случае берите сырые байты из responseBody и декодируйте их сами через ADODB.Stream с нужным Charset.

Тот же responseBody нужен для скачивания двоичных файлов: архивов, картинок, PDF. Его нельзя пропускать через строку, иначе байты исказятся. ADODB.Stream в двоичном режиме сохраняет их на диск как есть.

modHttp.basVB6
Public Sub DownloadFile(ByVal strUrl As String, ByVal strPath As String)
    Dim http As Object, stm As Object
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    http.setTimeouts 5000, 5000, 10000, 120000   ' большой файл: долгий приём
    http.Open "GET", strUrl, False
    http.send
    If http.Status <> 200 Then Err.Raise vbObjectError + 514, , "HTTP " & http.Status
    Set stm = CreateObject("ADODB.Stream")
    stm.Type = 1                                 ' adTypeBinary
    stm.Open
    stm.Write http.responseBody                  ' сырые байты ответа
    stm.SaveToFile strPath, 2                    ' adSaveCreateOverWrite
    stm.Close
End Sub
responseBody целиком держится в памяти. Для файлов в сотни мегабайт этот способ не подходит, их лучше качать через URLDownloadToFile из urlmon.dll.

HTTPS и TLS 1.2 на старых Windows

Код, который годами работал, однажды перестаёт подключаться к HTTPS-сайтам с ошибкой -2147012739 «An error occurred in the secure channel support». Сам код тут ни при чём: сервер отключил старые TLS 1.0 и 1.1, а клиентская Windows не умеет TLS 1.2 по умолчанию.

На Windows 7 и Server 2008 R2 для ServerXMLHTTP нужно установить обновление KB3140245 и прописать в реестре параметр DefaultSecureProtocols для WinHTTP. XMLHTTP, наоборот, подчиняется настройкам Internet Explorer, где TLS 1.2 включается флажком в свойствах браузера. На Windows XP TLS 1.2 недоступен вовсе, и единственный выход там это прокси или шлюз внутри сети.

Кэширование и зависание интерфейса

Ещё две ловушки встречаются особенно часто:

  • XMLHTTP кэширует GET-ответы через WinINet, и повторный запрос возвращает старые данные. Добавьте заголовок Cache-Control: no-cache или уникальный параметр в URL либо перейдите на ServerXMLHTTP;
  • синхронный запрос блокирует поток интерфейса: форма не перерисовывается, пока не придёт ответ. Короткие тайм-ауты и курсор ожидания делают это терпимым;
  • асинхронный режим (третий параметр True) с опросом readyState в таймере снимает блокировку, но усложняет код. Используйте его, если запросы долгие;
  • не создавайте объект на каждый запрос в цикле из тысяч итераций: переиспользуйте один экземпляр, вызывая Open заново.

Как это делают сегодня

В браузере тот же объект XMLHttpRequest давно уступил место функции fetch с промисами и async/await. В .NET HTTP-запросы выполняет HttpClient, лучше через IHttpClientFactory, с асинхронными методами GetStringAsync и PostAsync и поддержкой TLS 1.3. Если legacy-программу на VB6 пока нельзя переписать, сложные интеграции удобно вынести в небольшую утилиту или COM-видимую сборку на .NET и вызывать её из старого кода.

Вопросы и ответы

Чем XMLHTTP отличается от ServerXMLHTTP?

XMLHTTP работает через WinINet: наследует настройки браузера и кэширует ответы. ServerXMLHTTP работает через WinHTTP: без кэша, без окон, с тайм-аутами. Для программ и служб предпочтителен второй.

Почему вместо русского текста приходят вопросительные знаки?

Сервер не указал charset, и responseText декодировал ответ как UTF-8. Возьмите responseBody и преобразуйте его через ADODB.Stream с Charset = "windows-1251".

Какую ссылку подключить в проект?

Можно Microsoft XML, v6.0, тогда доступны типы MSXML2.ServerXMLHTTP60. Но позднее связывание через CreateObject надёжнее: программа не зависит от набора библиотек на машине разработчика.

Читайте также