HTTP-запросы из прикладного кода

Для новых интеграций используйте HttpPostEx и HttpGetEx. В отличие от старых HttpPost, HttpPostH и HttpGetH, функции с суффиксом Ex возвращают не только тело ответа, но и HTTP-статус, заголовки и cookies.

Сигнатуры

HttpPostEx(Url, PostData)
HttpPostEx(Url, PostData, Headers)

HttpGetEx(Url)
HttpGetEx(Url, Headers)

Параметры:

  • Url — полный URL, включая http:// или https://;
  • PostData — тело POST-запроса;
  • Headers — необязательный Record с заголовками запроса.

Обе функции возвращают Record.

Поля результата

Поле Тип Назначение
StatusCode Integer HTTP-статус ответа
Code Integer Совместимый псевдоним StatusCode
Ok Boolean True для кодов от 200 до 299
Body String Тело ответа
Headers String Полный набор заголовков ответа
SetCookie String Исходные значения заголовков Set-Cookie
Cookies String Строка cookies для заголовка следующего запроса

Поля можно читать как свойства или через Item:

code := Response.Code;
code := Response.Item('StatusCode') As Integer;
body := Response.Item('Body') As String;

Для универсального кода и новых полей рекомендуется Item('Имя').

Простой GET

Var
  Response: Record;
Begin
  Response := HttpGetEx('https://api.example.ru/status') As Record;

  If Not Response.Ok Then
    Raise(
      'HTTP ' + (Response.Code As String) + ': ' +
      (Response.Body As String)
    );

  Result := Response.Body As String;
End;

POST с JSON

Var
  Headers: Record;
  Response: Record;
  RequestBody: String;
Begin
  Headers.Item('Content-Type') := 'application/json; charset=utf-8';
  Headers.Item('Accept') := 'application/json';

  RequestBody := '{"name":"Тест"}';

  Response := HttpPostEx(
    'https://api.example.ru/items',
    ConvertToUTF8(RequestBody),
    Headers
  ) As Record;

  If Not Response.Ok Then
    Raise(
      'POST завершился с кодом ' + (Response.Code As String) +
      '. Ответ: ' + (Response.Body As String)
    );

  Result := Response.Body As String;
End;

ConvertToUTF8 нужен, когда тело формируется из внутренней строки платформы в Windows-1251, а сервер ожидает UTF-8. Не применяйте ConvertToUTF8 к уже готовому UTF-8 или к бинарным данным.

HTTP-статусы

Проверка только Code = 200 слишком узкая. Успешными могут быть:

  • 200 OK — обычный успешный ответ;
  • 201 Created — объект создан;
  • 202 Accepted — запрос принят в обработку;
  • 204 No Content — запрос выполнен, тело отсутствует.

Используйте Response.Ok, если интеграция не требует отдельной обработки каждого успешного статуса.

If Response.Ok Then
  Result := Response.Body As String
Else
  Raise('HTTP ' + (Response.Code As String));

Когда поведение зависит от статуса, используйте Case:

Case Response.Code Of
  200:
    Result := Response.Body As String;
  201:
    Result := Response.Body As String;
  401:
    Raise('Сервер отклонил авторизацию');
  403:
    Raise('Недостаточно прав для выполнения запроса');
  404:
    Raise('Ресурс не найден');
  500:
    Raise('Внутренняя ошибка сервера');
Else
  Raise(
    'Неожиданный HTTP-статус ' + (Response.Code As String) +
    ': ' + (Response.Body As String)
  );
End;

Коды 300–399 WinHTTP обычно обрабатывает автоматически. В результате возвращается статус последнего ответа цепочки. Cookies, полученные на промежуточных ответах перед редиректом, также собираются в SetCookie и Cookies.

Получение cookies

Сервер возвращает cookie заголовком Set-Cookie:

Set-Cookie: session=abc123; Path=/; HttpOnly

Платформа предоставляет два представления:

SetCookie = session=abc123; Path=/; HttpOnly
Cookies   = session=abc123

Для диагностики используйте SetCookie. Для следующего запроса используйте Cookies.

Response := HttpPostEx(LoginUrl, LoginBody, Headers) As Record;

Warning('Set-Cookie: ' + (Response.Item('SetCookie') As String));
Warning('Cookie: ' + (Response.Item('Cookies') As String));

Повторная отправка cookies

HttpPostEx и HttpGetEx не хранят прикладную сессию между независимыми вызовами. Приложение должно сохранить cookie и передать её заголовком Cookie.

Var
  SessionCookie: String;

Function Login(LoginUrl: String; LoginBody: String): String;
Var
  Headers: Record;
  Response: Record;
  NewCookies: String;
Begin
  Headers.Item('Content-Type') := 'application/json; charset=utf-8';

  Response := HttpPostEx(LoginUrl, LoginBody, Headers) As Record;

  // Cookie может прийти и при статусе, отличном от 200.
  NewCookies := Response.Item('Cookies') As String;
  If NewCookies <> '' Then
    SessionCookie := NewCookies;

  If Not Response.Ok Then
    Raise(
      'Ошибка входа. HTTP ' + (Response.Code As String) +
      ': ' + (Response.Body As String)
    );

  Result := Response.Body As String;
End;

Запрос с сохранённой cookie:

Function GetData(DataUrl: String): String;
Var
  Headers: Record;
  Response: Record;
  NewCookies: String;
Begin
  Headers.Item('Accept') := 'application/json';

  If SessionCookie <> NULL Then
    If SessionCookie <> '' Then
      Headers.Item('Cookie') := SessionCookie;

  Response := HttpGetEx(DataUrl, Headers) As Record;

  // Сервер может обновить cookie при любом запросе.
  NewCookies := Response.Item('Cookies') As String;
  If NewCookies <> '' Then
    SessionCookie := NewCookies;

  If Not Response.Ok Then
    Raise(
      'GET завершился с кодом ' + (Response.Code As String) +
      ': ' + (Response.Body As String)
    );

  Result := Response.Body As String;
End;

Важно:

  • сервер возвращает заголовок Set-Cookie, клиент отправляет Cookie;
  • в запрос передаётся Response.Cookies, а не Response.SetCookie;
  • не затирайте сохранённую cookie, если новый ответ вернул пустую строку;
  • сохраняйте cookie до проверки Ok, потому что сервер может установить или удалить её при 401, 403 или редиректе;
  • HttpOnly запрещает доступ к cookie из JavaScript браузера, но не мешает нативному HTTP-клиенту платформы получить и отправить её.

Диагностика

Временно выведите весь результат:

Warning('Code: ' + (Response.Code As String));
Warning('Headers: ' + (Response.Item('Headers') As String));
Warning('SetCookie: ' + (Response.Item('SetCookie') As String));
Warning('Cookies: ' + (Response.Item('Cookies') As String));
Warning('Body: ' + (Response.Item('Body') As String));

Если сервер не отвечает, не установлено соединение, истёк таймаут или произошла ошибка TLS, функция выбрасывает исключение. В таком случае Record с HTTP-статусом не создаётся, потому что HTTP-ответ от сервера не был получен.

HTTP-статус и транспортная ошибка — разные ситуации:

  • 401, 404, 500 — сервер ответил; код доступен в Response.Code;
  • timeout, отказ соединения, ошибка DNS/TLS — сервер не дал HTTP-ответа; выполнение прерывается исключением с диагностикой WinHTTP.

Совместимость

Добавление полей StatusCode, Code, Ok, Headers, SetCookie и Cookies не меняет массив параметров функций и не ломает старые вызовы. Старый код может продолжать читать только Body, а новый — использовать расширенный результат.