Перейти к содержанию
Fire Monkey от А до Я
  • 0

THTTPClient - непонятная ошибка при POST-е json на сервер


Bob32

Вопрос

Добрый день!

 

пытаюсь вот таким образом json-обьект запостить на сервер (это платежный шлюз).

получаю от сервера в респонзе ответ - 

 

{"Success":false,"ErrorCode":"5","Message":"Неверный запрос.","Details":"Неверный content-type application/x-www-form-urlencoded;charset=UTF-8. Необходимо отправлять запрос с указанием в заголовке application/json."}

"Неверный content-type application/x-www-form-urlencoded;charset=UTF-8. Необходимо отправлять запрос с указанием в заголовке application/json"

почему он неверный???? я же ниже в коде ставлю ContentType именно в application/json…..

 

Что я делаю не так?

 

Код:

 

 FHTTPClient:=THTTPClient.Create;
      FHTTPClient.ConnectionTimeout:=ConstHTTPClientConnectionTimeout;
      FHTTPClient.ResponseTimeout:=ConstHTTPClientResponseTimeout;
      FHTTPClient.UserAgent:='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
      FHTTPClient.Accept:='text/html,application/json,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8';
      FHTTPClient.AcceptEncoding:='gzip, deflate';
      FHTTPClient.AcceptLanguage:='ru,en-US;q=0.8,en;q=0.6';

      FHTTPClient.ContentType:='application/json';
      FHTTPClient.AcceptCharSet := 'UTF-8';

      try

        try
          HTTPResponse:=FHTTPClient.Post('https://securepay.tinkoff.ru/v2/Init', Memo1.Lines);
          if Assigned(HTTPResponse) and (HTTPResponse.StatusCode = 200) then
            begin
            //  if Assigned(fmRating.Image14.Bitmap) then fmRating.Image14.Bitmap.Free;

              Memo2.Lines.LoadFromStream(HTTPResponse.ContentStream);

            end;
        except

        end;
      finally
        if Assigned(FHTTPClient) then FHTTPClient.Free;
      end;

Ссылка на комментарий

Рекомендуемые сообщения

  • 0

вот эту структуру можно залить в Memo1 - тогда и менять в ней ничего не надо

вот это не нужно:

Str:=Str.Replace('%Amount%',IntToStr(StrToInt(Edit4.Text)*100),[rfReplaceAll]);
  Str:=Str.Replace('%OrderId%',IntToStr(OrderId));
  Str:=Str.Replace('%Email%',fmSettings.Edit5.Text,[rfReplaceAll]);
  Str:=Str.Replace('%Phone%',fmSettings.Edit4.Text,[rfReplaceAll]);


{
    "TerminalKey": "1595881652554DEMO",
    "Amount": "140000",
    "OrderId": "2105094",
    "Description": "Подарочная карта на 1400.00 рублей",
    "DATA": {
        "Phone": "+71234567890",
        "Email": "a@test.com"
    },
    "Receipt": {
        "Email": "a@test.ru",
        "Phone": "+79031234567",
        "EmailCompany": "b@test.ru",
        "Taxation": "osn",
        "Items": [
            {
                "Name": "Наименование товара 1",
                "Price": 10000,
                "Quantity": 1.00,
                "Amount": 10000,
                "PaymentMethod": "full_prepayment",
                "PaymentObject": "commodity",
                "Tax": "vat10",
                "Ean13": "0123456789"
            },
            {
                "Name": "Наименование товара 2",
                "Price": 20000,
                "Quantity": 2.00,
                "Amount": 40000,
                "PaymentMethod": "prepayment",
                "PaymentObject": "service",
                "Tax": "vat20"
            },
            {
                "Name": "Наименование товара 3",
                "Price": 30000,
                "Quantity": 3.00,
                "Amount": 90000,
                "Tax": "vat10"
            }
        ]
    }
}
 

Ссылка на комментарий
  • 0
4 минуты назад, krapotkin сказал:

1975142031_QIPShot-Screen247.png.c9f0cd1b1d93479e6a52f4f67cd0c8e7.png httpDemo.7z 32 \u043a\u0411 · 0 загрузок

вот демо 

работает одинаково на Windows и Android

на Берлине? у меня Рио.  в этом тоже может быть дело. и на Рио под ИОС-ом тоже работает. под АНдроидом - нет. может как-то изголиться и по другому из реквестконтента сгружать данные до парсера?

Изменено пользователем Bob32
Ссылка на комментарий
  • 0
48 минут назад, krapotkin сказал:

код приложен, запускайте и играйтесь до просветления

Спасибо Вам огромное за время и помощь!

я сделаю, как в вашем коде.

справедливости ради , этот Post  (с двумя стримами, из которых второй докачиватся в фоне) - это другая функция этого класса. в той есть баг. это моё мнение )

r := h.Post(URL, stSrc, stRes);

Ссылка на комментарий
  • 0

он не в фоне докачивается, а синхронно

но он точно рабочий)

а вот сам запрос сделан именно в фоне. собсно как и должно быть всегда

Изменено пользователем krapotkin
Ссылка на комментарий
  • 0
2 часа назад, krapotkin сказал:

он не в фоне докачивается, а синхронно

но он точно рабочий)

а вот сам запрос сделан именно в фоне. собсно как и должно быть всегда

просто для моего понимания. вот смотрите - человек в программе нажал на кнопку - оплатить. больше сейчас делать ему нечего.он все равно будет ждать. так ли много смысла это запускать в фоне? длится для пользователя это будет все равно одни и те же доли секунды.

Ссылка на комментарий
  • 0
4 часа назад, krapotkin сказал:

код приложен, запускайте и играйтесь до просветления

можно вопрос для понимания?

r := h.Post(URL, stSrc, stRes);
    if r.StatusCode=200 then
    begin
      x := so(stRes.DataString);
      tthread.Synchronize(nil,
        procedure
        begin
          m1.Lines.Text := x.AsJSON(True);
        end);
    end
    else
    begin
      tthread.Synchronize(nil,
        procedure
        begin
          m1.Lines.Add(r.StatusText);
        end);

 

вот в этом коде у  POST - второй параметр. это значит, что 

If you want to receive the response data as your HTTP client downloads it from the target server, instead of waiting for your HTTP client to download the whole data, use the AResponseContent parameter to specify a stream to receive the downloaded data. Alternatively, you can wait for your HTTP client to download the whole response data, and obtain the response data as a stream from the ContentStream property of the response object that Get returns.

Regardless of whether you receive the data as it comes or wait for the whole data to be available, you can handle the OnReceiveData event to track the progress of the download of the response data.

 

….надо по окончанию загрузки  - ловить момент обработчиком на OnReceiveData. разве нет?

как эта схема с tthread.Synchronize в данном случае работает? ВОТ ЭТОГО НЕ ПОНИМАЮ! )

 

……..

мне кажется  - она работает просто потому, что контент маленький и пулей загружается.

Изменено пользователем Bob32
Ссылка на комментарий
  • 0

Вся эта процедура в моем коде запущена в отдельном потоке через TTask.Run(). (кстати - асинхронно! т.е. сначала скорее всего выполнится до конца FormCreate а потом запустится run)

Поэтому доступ к визуальным компонентам должен быть только через синхронизацию. Для этого вывод в memo обернут в TThread.Synchronize();

Далее. В этой форме POST второй параметр - стрим, данные из которого мы передаем на сервер, а третий - стрим, куда придет результат.

Так как мы знаем, что туда и обратно будет ходить текст, то и выбираем TStringStream, как самый подходящий.

Т.е. в stRes нас будет ждать результат. Такой же как и в R.ContentAsString

Если вы хотите отслеживать (track) процесс получения данных, то можно повеситься на OnReceiveData там будет сообщаться сколько байт уже скачано, сколько всего планируется скачать. В этом обработчике можно сделать обновление progressBar, тоже через синхронизацию. Предполагается, что у вас там хотя бы сотни килобайт данных, иначе сомневаюсь, что этот обработчик хоть раз сработает. Ловить окончание с его помощью точно не нужно )

Все процессы обмена обычно делятся на синхронные и асинхронные. Синхронные - программа дальше не идет, пока не выполнится эта строчка. Асинхронные - команду отдали и пошли дальше. В этом случае обычно задается кусок кода, который выполнится по окончании процесса. 

THttpClient имеет оба вида вызовов. Обычные синхронные Get, Post и т.д. Асинхронные - BeginGet, BeginPost и т.д.

http://docwiki.embarcadero.com/Libraries/Rio/en/System.Net.HttpClient.THTTPClient_Methods

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

Если вы будете это делать из главного потока, то можно использовать асинхронные. Вопрос удобства и архритектуры.

Но говорить, что "что там, фигня, один маленький запросик" нельзя. Если сервер недоступен, не отвечает, плохая связь и т.д., ваша программа тупо зависнет сек на 30.

Несколько раз так зависнет, а потом Андроид предлагает удалить ее с устройства, т.к. в ней  баги и вообще она плохая. Пользователи не очень любят такое))

 

Ссылка на комментарий
  • 0

Спасибо за пояснение!

но разве из хелпа не следует, что в случае поялвения второго стрима в парамтерах POST - основной поток не будет ждать и пойдет дальше? (см текст выше. я это понял так.)

Ссылка на комментарий
  • 0

нет не следует

там написано, "если вы хотите получать данные прямо в процессе их получения, вместо того чтобы ждать когда скачается всё до конца..."

ну собсно, да, можно из того же стрима их забирать в событии OnReceiveData, но зачем вам неполный json ?? это же не звук, не видео, которые можно сразу на экран выводить, не дожидаясь всего кино...

но при этом это будет происходить независимо, опять же в другом потоке. ваш поток, где вы вызвали Post, будет ждать окончания загрузки.

Асинхронно это будет только через BeginPost

Ссылка на комментарий
  • 0

можете кидаться в меня тухлыми помидорами, но я этот турецкий модуль на 3400 строк с амбициозными названиями SuperObject использовать не стал! )

 

вот так код выглядит на нашем рабоче-крестьянском уровне! и он работает! )

 

  Str:=Memo1.Lines.Text;
  Str:=Str.Replace('%Amount%',IntToStr(StrToInt(Edit4.Text)*100),[rfReplaceAll]);
  Str:=Str.Replace('%OrderId%',IntToStr(OrderId));
  Str:=Str.Replace('%Email%',fmSettings.Edit5.Text,[rfReplaceAll]);
  Str:=Str.Replace('%Phone%',fmSettings.Edit4.Text,[rfReplaceAll]);


  stSrc := TStringStream.Create('', TEncoding.UTF8);
  stRes := TStringStream.Create('', TEncoding.UTF8);
  stSrc.WriteString(Str);
  stSrc.Position := 0;

  FHTTPClient:=THTTPClient.Create;

  FHTTPClient.ConnectionTimeout:=ConstHTTPClientConnectionTimeout;
  FHTTPClient.ResponseTimeout:=ConstHTTPClientResponseTimeout;
  FHTTPClient.Accept:='application/json';
  FHTTPClient.ContentType:='application/json';
  FHTTPClient.AcceptCharSet := 'UTF-8';
  FHTTPClient.HandleRedirects := false;

    try
    try
      HTTPResponse:=FHTTPClient.Post({'https://umka.space/api/index.php'}'https://securepay.tinkoff.ru/v2/Init',stSrc, stRes);
      if (HTTPResponse.StatusCode = 200) then
        begin
          Str:=stRes.DataString;
          Memo2.Lines.Text := Str;
        end
      else
        begin

        end;

    except
      Memo2.Lines.Clear;
    end;
  finally
    if Assigned(FHTTPClient) then FHTTPClient.Free;
    BytesStream.Free;
    stSrc.Free;
    stRes.Free;
  end;

  JSON:=TJSONObject(TJSONObject.ParseJSONValue(Str));

Ссылка на комментарий
  • 0
7 минут назад, slav_z сказал:

if Assigned(FHTTPClient) then FHTTPClient.Free;    замените на просто  FHTTPClient.Free

Хорошо. ) просто я перестал писать программы на Дельфи ещё при Ельцине ) сейчас пришлось вернуться обратно. Ельцина нет, а привычки остались ) 

Ссылка на комментарий
  • 0
13 часов назад, Bob32 сказал:

синхронная и будет ждать

синхронная концепция вызова и синхронизация потоков не имеют ничего общего

то, что программа не пойдет дальше, пока не закончится POST, никак не отменяет того, что вызовы экранных компонентов должны происходить только в главном потоке

поэтому TThread.Synchronize

Ссылка на комментарий
  • 0
2 часа назад, krapotkin сказал:

синхронная концепция вызова и синхронизация потоков не имеют ничего общего

то, что программа не пойдет дальше, пока не закончится POST, никак не отменяет того, что вызовы экранных компонентов должны происходить только в главном потоке

поэтому TThread.Synchronize

я понимаю, что учить меня Вам наверное не очень интересно, но простите меня пожалуйста и за еще один вопрос:

а физически какая разница существует между вот таким присвоением TMemo (или любого другого визуального компонента) через TThread.Synchronize -  и простым присваиванием без него?

что вообще на самом деле этот TThread.Synchronize делает?

 

…..

кстати, события этого кода происходят просто по кнопке в одной из форм приложения. это же основной поток?

Ссылка на комментарий
  • 0

Задача любой синхронизации - не дать разным потокам одновременно изменять данные.

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

Ровно так же и с объектами программы. Если к ним есть доступ у нескольких потоков, то результат взаимодействия почти гарантированно приведет к ошибке программы.

Synchronize ставит действие в очередь на ожидание главного потока. Как только тот освобождается, он выполняет это действие и оба потока идут дальше. Главный - своей дорогой, вызвавший Synchronize - своей.

Достаточно просто запомнить - экран - один, вызов только через синхронизацию.

Более полное объяснение заповедей многопоточного программирования несложно найти в сети)

Ссылка на комментарий
  • 0

сорри, у еще один вопрос по этой теме ) 

у меня в программе общение с сервером происходит в отдельном потоке, когда надо что-то получить - я из основного в тот (в очередь) команду с параметрами ставлю - в том потоке это обрабатывается, когда приходит ответ - вызывается событие onReceiveData, на которое я вешаю обработчик (это процедура из основной формы), в ней CASE на 100+ команд, и по каждой я вызываю какую то процедуру на какой-то из форм и она что-то там отрисовывает.

 

мне нужно в каждой из этих процедур делать свой TThread.Synchronize - там, где я визуальные компоненты меняю, или один раз это сделать в onReceiveData, - фактически - этот CASE of ….. и вызовы 100 процедур в него поместить?

как правильно?

Ссылка на комментарий
  • 0

любым способом

вплоть до оборачивать каждое обращение к формам в отдельный Synchronize

можно также воспользоваться асинхронным методом TThread.Queue

для освежения интерфейса мне кажется он более полезным, потому что не блокируется выполнение потока, он не ждет синхронизации.

Ссылка на комментарий
  • 0

ну если весь вот этот ужас )   (а это треть этой процедуры)  я в один TThread.Synchronize  оберну - мне это зачтётся?  )

 

или надо идти внутрь каждой вот например из этих процедур и там это делать? все они меняют данные на формах.

    TCommand.GetMyQuests : fmQuestsEditor.LoadMyQuestsInListView(ARequest);
    TCommand.GetAllQuests : ListViewUpdate(ARequest);
    TCommand.GetQuest : fmQuestsEditor.LoadQuestData(ARequest);
    TCommand.DeleteQuest,
    TCommand.CopyQuest : if ARequest.Error then fmQuestsEditor.lbError.Text:='Connection problems... #32'{+ARequest.ErrorMsg} else fmQuestsEditor.LoadMyQuests;
    TCommand.GetQuestsPOI : GetQuestsPOI(ARequest);
    TCommand.GetPOI : GetPOI(ARequest);
    TCommand.LoadQuestLocally : LoadQuestLocally(ARequest);
    TCommand.ProcessGameData : ;                                ////////////////////////////////////
    TCommand.GetAuthorGames : fmFinance.LoadFinanceListView(ARequest);
    TCommand.GetUserMoneyRequests : fmMoneyRequest.LoadMoneyRequestListView(ARequest);
    TCommand.ProcessPaymentRequest: fmMoneyRequest.ProcessPaymentRequest(ARequest);
    TCommand.GetOpenRequests : fmOpenRequests.LoadMoneyRequestListView(ARequest);
    TCommand.ProcessOpenPaymentRequest: fmOpenRequests.ProcessOpenPaymentRequests(ARequest);
    TCommand.GetUserDetails : GetUserDetails(ARequest);

мне конечно проще - один раз все это обернуть.

 

procedure TfmMain.OnReceiveData(const ARequest : TRequest);
begin
  if ARequest.Error then
    begin
      lbError.Text:='Connection problems... #32';//+ARequest.ErrorMsg;
    //    exit;
    end;

  case ARequest.Command of
    TCommand.Log :
      begin
     // Log(ARequest.Content);
      end;

    TCommand.GetMessagesFromDataBase : ;

    TCommand.GetMessages : GetMessages(ARequest);

    TCommand.AddQuestTask:
      if ARequest.Error then fmQuestsEditor.Label25.Text:='Connection problems... #32'//+ARequest.ErrorMsg
      else
        begin
          fmQuestsEditor.Label25.Text:='Задание успешно добавлено!';
          fmQuestsEditor.edTasksQty.Text:=IntToStr(StrToInt(Trim(fmQuestsEditor.edTasksQty.Text))+1);
          fmQuestsEditor.edCurrentTask.Text:=IntToStr(StrToInt(Trim(fmQuestsEditor.edCurrentTask.Text))+1);
          fmQuestsEditor.edTasksQtyChange(Self);
          fmQuestsEditor.CheckBox1.IsChecked:=False;
          fmQuestsEditor.CheckBox2.IsChecked:=False;
          fmQuestsEditor.CheckBox3.IsChecked:=False;
          fmQuestsEditor.CheckBox4.IsChecked:=False;
          fmQuestsEditor.Memo1.Text:='';
          fmQuestsEditor.Edit2.Text:='';
        end;
    TCommand.DeleteQuestTask:
      begin
        fmQuestsEditor.SpeedButton11.Enabled:=True;
        if ARequest.Error then fmQuestsEditor.Label25.Text:='Connection problems... #32'//+ARequest.ErrorMsg
        else
          begin
            fmQuestsEditor.edTasksQty.Text:=IntToStr(StrToInt(Trim(fmQuestsEditor.edTasksQty.Text))-1);
            fmQuestsEditor.edTasksQtyChange(Self);
            fmQuestsEditor.CheckBox1.IsChecked:=False;
            fmQuestsEditor.CheckBox2.IsChecked:=False;
            fmQuestsEditor.CheckBox3.IsChecked:=False;
            fmQuestsEditor.CheckBox4.IsChecked:=False;
            fmQuestsEditor.Memo1.Text:='';
            fmQuestsEditor.Edit2.Text:='';
            fmQuestsEditor.edCurrentTaskChange(Self);
            fmQuestsEditor.Label25.Text:='Задание удалено!';
          end;
      end;
    TCommand.MoveTaskToLeft:
      begin
        fmQuestsEditor.SpeedButton14.Enabled:=True;
        if ARequest.Error then fmQuestsEditor.Label25.Text:='Connection problems... #32'//+ARequest.ErrorMsg
        else
           begin
             fmQuestsEditor.edCurrentTask.Value:=fmQuestsEditor.edCurrentTask.Value-1;
             fmQuestsEditor.Label25.Text:='Задание перемещено!';
           end;
      end;
    TCommand.MoveTaskToRight:
      begin
        fmQuestsEditor.SpeedButton16.Enabled:=True;
        if ARequest.Error then fmQuestsEditor.Label25.Text:='Connection problems... #32'//+ARequest.ErrorMsg
        else
          begin
            fmQuestsEditor.edCurrentTask.Value:=fmQuestsEditor.edCurrentTask.Value+1;
            fmQuestsEditor.Label25.Text:='Задание перемещено!';
          end;
      end;
    TCommand.UpdateQuestTask: if ARequest.Error then fmQuestsEditor.Label25.Text:='Connection problems... #32'{+ARequest.ErrorMsg} else fmQuestsEditor.Label25.Text:='Задание сохранено!';

   // TCommand.GetQuestTask: GetQuestTask(ARequest);
    TCommand.GetQuestTask4Edit: fmQuestsEditor.LoadTaskData(ARequest);

    TCommand.GetImage :
      begin
       // Memo1.Lines.Add({GetFileNameFromRequest(}ARequest.Query{)}+'-');  ////////////////
        if Trim(GetFileNameFromRequest(ARequest.Query))='' then
          begin
            lbError.Text:='Ошибка!Некорректное имя файла #172';
            exit;
          end;

        imBuffer.Bitmap.Assign(ARequest.BitmapSurface);

        imBuffer.Bitmap.SaveToFile(TPath.Combine(TPath.GetDocumentsPath, GetFileNameFromRequest(ARequest.Query)));
        if Assigned(ARequest.BitmapSurface) then ARequest.BitmapSurface.Free;
        

        { этот код был в онлайн версии 
        imMediaFile.Bitmap.Assign(ARequest.BitmapSurface);
        imMediaFile.Visible:=True;
        if Assigned(ARequest.BitmapSurface) then ARequest.BitmapSurface.Free;}
      end;
    TCommand.GetImageWithCheck :
      begin
        fmQuestsEditor.Image1.Bitmap.Assign(ARequest.BitmapSurface);
        fmQuestsEditor.Memo1.Visible:=False;
        fmQuestsEditor.SpeedButton27.Visible:=False;
        fmQuestsEditor.Image1.Visible:=True;
        fmQuestsEditor.SpeedButton18.Visible:=True;
        if Assigned(ARequest.BitmapSurface) then ARequest.BitmapSurface.Free;
      end;
    TCommand.UpdateUser :  UpdateUser(ARequest);
    TCommand.GetTopPlayers : GetTopPlayers(ARequest);
    TCommand.AddQuest :
      begin
        fmQuestsEditor.GetQuestId(ARequest);
        fmQuestsEditor.NewQuest:=False;
        fmQuestsEditor.SpeedButton6.Enabled:=True;
      end;
 

 

 

….

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...