POST, отправленный Delphi 5, сломал мой GraphQL PHP API

Я сделал простой API на PHP, чтобы получать некоторые переменные из запросов GET / POST и отправлять их в сторонний GraphQL API. Но у меня проблемы с кодировкой контента.

Приложение работает так:

Настольное приложение Delphi 5 отправляет простой GET или POST с некоторыми переменными в мой PHP API, а мой PHP API делает запрос к стороннему GraphQL API.

Я отправляю GraphQL:

query {
  node(id: 1){
    ... on Organization{
      fullname
      entities(type: STUDENT, search: "Some Student Name"){
        nodes{
          dbId
          fullname
          eid
        }
      }
    }
  }
}

Пример URL-адреса запроса:

http: //api-link/request.php? token = xyzwsa & id = 17 & tipo = boleto & mat = 123456 & nome_aluno = Некоторые% 20Name% 20 Здесь & nome_responsavel = Другой% 20Name & titulo = Just% 20a% 20test & numero = 001122 & venc = 2018-04000 2000000.000000% 2000000.000000% 200% 2000000000000000

Когда я копирую и вставляю URL-адрес запроса в браузер или вызываю его в командной строке cURL, работает нормально.

Но когда Delphi 5 вызывает его, моя строка запроса прерывается множеством знаков "+" (плюс):

Syntax Error GraphQL request (1:6) Cannot parse the unexpected character "+".

1: query+%7B%0D%0A++node%28id%3A+1%29%7B%0D%0A++++...+on+Organization%7B%0D%0A++++++fullname%0D%0A++++++entities%28type%3A+STUDENT%2C+search%3A+%22Some Student Name%22%29%7B%0D%0A++++++++nodes%7B%0D%0A++++++++++dbId%0D%0A++++++++++fullname%0D%0A++++++++++eid%0D%0A++++++++%7D%0D%0A++++++%7D%0D%0A++++%7D%0D%0A++%7D%0D%0A%7D
        ^

Приложение Delphi 5 использует для этого компонент TidHTTP. Код ниже:

procedure TForm1.ExportaClassApp(prID : Integer; prNumBoleto, prLinhaDigitavel : String);
const cUrl         = 'http://api-link/request.php?';
      cToken       = 'xyzwsa';
      cTipo        = 'boleto';
      cE_Comercial = #138; (* equivalent to & *)

var sSQL              : String;
    qryPesquisa       : TADOQuery;
    oHTTP             : TIdHTTP;
    sRetornoClassApp  : String;
    HTTPClient        : TidHTTP;
    Lista             : TStringStream;
    sParametros       : String;

    Url_Completa      : String;
    sToken            : String;
    sId               : String;
    sTipo             : String;
    sMatricula        : String;
    sNome_Aluno       : String;
    sNome_Responsavel : String;
    sDescricao        : String;
    sNumBoleto        : String;
    sVencimento       : String;
    sValor            : String;
    sLinhaDigitavel   : String;
begin
   oHTTP      := TIdHTTP.Create(Application);
   HTTPClient := TidHTTP.Create(Application);;
   sDescricao := '';

   sDescricao := Copy(sDescricao,1, length(sDescricao) - 2);

   sToken            := 'token='             + cToken;
   sId               := '&id='               + IntToStr(prId);
   sTipo             := '&tipo='             + cTipo;
   sMatricula        := '&mat='              + '123456';
   sNome_Aluno       := '&nome_aluno='       + 'Some Student Name';
   sNome_Responsavel := '&nome_responsavel=' + 'Another Name';
   sDescricao        := '&titulo='           + 'Just a test';
   sNumBoleto        := '&numero='           + prNumBoleto;
   sVencimento       := '&venc='             + '2018-04-02';
   sValor            := '&valor='            + FloatToStr(843 * 100);
   sLinhaDigitavel   := '&linha='            + prLinhaDigitavel;

   sParametros  := sToken + sId + sTipo + sMatricula + sNome_Aluno + sNome_Responsavel + sDescricao + sNumBoleto + sVencimento + sValor + sLinhaDigitavel;
   Url_Completa := cUrl + sPArametros;
   Url_Completa := StringReplace(Url_Completa,' ','%20',[rfReplaceAll, rfIgnoreCase]);

    if edtContentType.Text <> '' then
       oHTTP.Request.ContentType := edtContentType.Text
    else
       oHTTP.Request.ContentType := '';

    if edtContentEncoding.Text <> '' then
       oHTTP.Request.ContentEncoding := edtContentEncoding.Text
    else
       oHTTP.Request.ContentEncoding := '';    

   sRetornoClassApp := oHTTP.URL.URLDecode(oHTTP.Get(Url_Completa));

   btnLimparClick(Self);

   mmoEnvio.Text   := Url_Completa;
   mmoRetorno.Text := sRetornoClassApp;

   FreeAndNil(oHTTP);
end;

Вот заголовки запроса / ответа браузера / cURL (которые работают):

Array
(
    [0] => POST /graphql HTTP/1.1
    [1] => Host: joy.classapp.co
    [2] => User-Agent: PHP Curl/1.6 (+https://github.com/php-mod/curl)
    [3] => Accept: */*
    [4] => Content-Length: 400
    [5] => Content-Type: application/x-www-form-urlencoded
)
Array
(
    [0] => HTTP/1.1 200 OK
    [1] => Access-Control-Allow-Origin: *
    [2] => Content-Type: application/json; charset=utf-8
    [3] => Date: Mon, 02 Apr 2018 14:37:19 GMT
    [4] => ETag: W/"4d-r2dRUM/0NEHToQUzFDAesWSzSWY"
    [5] => Server: nginx/1.12.1
    [6] => X-Content-Type-Options: nosniff
    [7] => X-DNS-Prefetch-Control: off
    [8] => X-Download-Options: noopen
    [9] => X-Frame-Options: SAMEORIGIN
    [10] => X-XSS-Protection: 1; mode=block
    [11] => Content-Length: 77
    [12] => Connection: keep-alive
)

И ниже заголовков запросов / ответов Delphi 5 TidHTTP (это не работает):

Array
(
    [0] => POST /graphql HTTP/1.1
    [1] => Host: joy.classapp.co
    [2] => User-Agent: PHP Curl/1.6 ( https://github.com/php-mod/curl)
    [3] => Accept: */*
    [4] => Content-Length: 407
    [5] => Content-Type: application/x-www-form-urlencoded
)
Array
(
    [0] => HTTP/1.1 400 Bad Request
    [1] => Access-Control-Allow-Origin: *
    [2] => Content-Type: application/json; charset=utf-8
    [3] => Date: Mon, 02 Apr 2018 14:47:53 GMT
    [4] => ETag: W/"1f6-RtlkHyZy4MNsaj0EjU9LCzebnSs"
    [5] => Server: nginx/1.12.1
    [6] => X-Content-Type-Options: nosniff
    [7] => X-DNS-Prefetch-Control: off
    [8] => X-Download-Options: noopen
    [9] => X-Frame-Options: SAMEORIGIN
    [10] => X-XSS-Protection: 1; mode=block
    [11] => Content-Length: 502
    [12] => Connection: keep-alive
)

Я попытался изменить Content-Type em Curl Class (я использую библиотеку Curl / Curl), чтобы сделать POST для GraphQL API, безуспешно.

Я не знаю, почему он отлично работает в браузере / cURL, а не в Delphi 5. Проблема в моем PHP API или в компоненте Delphi 5 TidHTTP? Или оба?

Обновлено: пробовал как Delphi 5, так и Delphi 2006 с Indy v10.1.5, используя HTTP-глаголы GET и POST. Та же ошибка.

Спасибо за внимание.

Покажите, пожалуйста, актуальный код Delphi, который у вас не работает. Delphi 5 ОЧЕНЬ-ОЧЕНЬ старый, используете ли вы версию Indy, которая поставляется с Delphi 5, или вы используете последнюю современную версию?

Remy Lebeau 02.04.2018 17:04

@RemyLebeau Пробовал как Delphi 5, так и Delphi 2006 с Indy v10.1.5, используя HTTP-глаголы GET и POST. Та же ошибка. Я отредактировал свой пост кодом Delphi.

Lord_Dracon 02.04.2018 19:02

Почему вы для начала используете такие устаревшие версии? Delphi 5 почти 20 лет, а Indy 10.1.5 почти столько же лет. Текущая версия Indy - 10.6.2. За последнее десятилетие в TIdHTTP было внесено МНОГО изменений и исправлений.

Remy Lebeau 02.04.2018 19:27

Кроме того, сбой, с которым вы столкнулись, связан с запросом POST, но вместо этого ваш код Delphi отправляет запрос GET. Вы не показали содержимое тела POST или код PHP, который получает GET, а затем генерирует POST. Таким образом, существует несколько точек возможного сбоя, но вы не предоставили достаточно диагностических данных, чтобы выяснить, какая из них действительно дает сбой.

Remy Lebeau 02.04.2018 19:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
4
471
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Есть несколько проблем с вашим кодом Delphi.

  • Вы создаете экземпляры двух объектов TIdHTTP, но используете только один из них и пропускаете другой.

  • вы должны использовать TIdURI для форматирования параметров URL-запроса, а не StringReplace().

  • вам не нужно и не следует использовать TIdURI для декодирования результата TIdHTTP.Get(). Просто используйте результат как есть.

Попробуй это:

procedure TForm1.ExportaClassApp(prID : Integer; prNumBoleto, prLinhaDigitavel : String);
const
  cUrl         = 'http://api-link/request.php?';
  cToken       = 'xyzwsa';
  cTipo        = 'boleto';
var
  oHTTP             : TIdHTTP;
  sToken            : String;
  sId               : String;
  sTipo             : String;
  sMatricula        : String;
  sNome_Aluno       : String;
  sNome_Responsavel : String;
  sDescricao        : String;
  sNumBoleto        : String;
  sVencimento       : String;
  sValor            : String;
  sLinhaDigitavel   : String;
  sParametros       : String;
  Url_Completa      : String;
  sRetornoClassApp  : String;
begin
  sToken            := 'token='             + cToken;
  sId               := '&id='               + IntToStr(prId);
  sTipo             := '&tipo='             + cTipo;
  sMatricula        := '&mat='              + '123456';
  sNome_Aluno       := '&nome_aluno='       + TIdURI.ParamsEncode('Some Student Name');
  sNome_Responsavel := '&nome_responsavel=' + TIdURI.ParamsEncode('Another Name');
  sDescricao        := '&titulo='           + TIdURI.ParamsEncode('Just a test');
  sNumBoleto        := '&numero='           + TIdURI.ParamsEncode(prNumBoleto);
  sVencimento       := '&venc='             + '2018-04-02';
  sValor            := '&valor='            + FloatToStr(843 * 100);
  sLinhaDigitavel   := '&linha='            + TIdURI.ParamsEncode(prLinhaDigitavel);

  sParametros  := sToken + sId + sTipo + sMatricula + sNome_Aluno + sNome_Responsavel + sDescricao + sNumBoleto + sVencimento + sValor + sLinhaDigitavel;
  Url_Completa := cUrl + sParametros;

  oHTTP := TIdHTTP.Create(nil);
  try
    oHTTP.Request.ContentType := edtContentType.Text;
    oHTTP.Request.ContentEncoding := edtContentEncoding.Text;
    sRetornoClassApp := oHTTP.Get(Url_Completa);
  finally
    oHTTP.Free;
  end;

  btnLimparClick(Self);

  mmoEnvio.Text   := Url_Completa;
  mmoRetorno.Text := sRetornoClassApp;
end;

Альтернативно:

procedure TForm1.ExportaClassApp(prID : Integer; prNumBoleto, prLinhaDigitavel : String);
const
  cUrl         = 'http://api-link/request.php?';
  cToken       = 'xyzwsa';
  cTipo        = 'boleto';
var
  oHTTP             : TIdHTTP;
  sParametros       : String;
  Url_Completa      : String;
  sRetornoClassApp  : String;
begin
  sParametros  := Format('token=%s&id=%d&tipo=%s&mat=%s&nome_aluno=%s&nome_responsavel=%s&titulo=%s&numero=%s&venc=%s&valor=%f&linha=%s',
    [cToken,
     prId,
     cTipo,
     '123456',
     TIdURI.ParamsEncode('Some Student Name'),
     TIdURI.ParamsEncode('Another Name'),
     TIdURI.ParamsEncode('Just a test'),
     TIdURI.ParamsEncode(prNumBoleto),
     '2018-04-02',
     843 * 100,
     TIdURI.ParamsEncode(prLinhaDigitavel)]
  );

  Url_Completa := cUrl + sParametros;

  oHTTP := TIdHTTP.Create(nil);
  try
    oHTTP.Request.ContentType := edtContentType.Text;
    oHTTP.Request.ContentEncoding := edtContentEncoding.Text;
    sRetornoClassApp := oHTTP.Get(Url_Completa);
  finally
    oHTTP.Free;
  end;

  btnLimparClick(Self);

  mmoEnvio.Text   := Url_Completa;
  mmoRetorno.Text := sRetornoClassApp;
end;
Ответ принят как подходящий

Мы определили, что эта проблема возникает только со струнами с акцентом. Итак, после небольшого исследования, мы нашел решение (возможно, не самый элегантный), чтобы заменить символы ASCII на спецификацию RFC 3986 (например, пробел превращается в% 20 вместо "+"), и мы применяем эту функцию только к переменным, которые может иметь некоторую акцентуацию:

sNome_Aluno       := '&nome_aluno='       + fnstUrlEncodeUTF8('Some Student Name');
sNome_Responsavel := '&nome_responsavel=' + fnstUrlEncodeUTF8('Another Name');
sDescricao        := '&titulo='           + fnstUrlEncodeUTF8('Just a test');

И это сработало.

Также мы удалили второй экземпляр TIdHTTP, упомянутый Реми Лебо.

Спасибо.

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

Другие вопросы по теме