Загрузить на веб-сервер Skip

Я пытаюсь загрузить файл JSON на веб-сервер SKIP (getskip.com) и получаю ошибку сокета 10054. Они не очень хорошо дают примеры, поскольку думают, что все используют cUrl, но мы все еще используя Delphi XE6 с Indy.

Вот что они предоставили.

Для Java с использованием OK http:

OkHttpClient client = new OkHttpClient();

MediaType mediaType = MediaType.parse("multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
RequestBody body = RequestBody.create(mediaType, "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"path\"; filename=\"<<file_name>>\"\r\nContent-Type: application/vnd.novadigm.ext\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"data\"\r\n\r\n[{\"file_key\": \"path\", \"store_id\"::<<store_id>>}]\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--");
Request request = new Request.Builder()
  .url("https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json")
  .post(body)
  .addHeader("content-type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW")
  .addHeader("Authorization", "Bearer <<api_user_token>>")
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("User-Agent", "PostmanRuntime/7.13.0")
  .addHeader("Accept", "*/*")
  .addHeader("Cache-Control", "no-cache")
  .addHeader("Postman-Token", "de9932d2-bda0-4df6-8a9a-e2d4f74c2048,d59a9861-1a65-471a-8ada-6e025e985dca")
  .addHeader("Host", "upload.goskip.com:8080")
  .addHeader("accept-encoding", "gzip, deflate")
  .addHeader("content-length", "407")
  .addHeader("Connection", "keep-alive")
  .addHeader("cache-control", "no-cache")
  .build();

Response response = client.newCall(request).execute();

Java с использованием Unirest:

HttpResponse<String> response = Unirest.post("https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json")
  .header("content-type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW")
  .header("Authorization", "Bearer <<api_user_token>>")
  .header("Content-Type", "application/x-www-form-urlencoded")
  .header("User-Agent", "PostmanRuntime/7.13.0")
  .header("Accept", "*/*")
  .header("Cache-Control", "no-cache")
  .header("Postman-Token", "de9932d2-bda0-4df6-8a9a-e2d4f74c2048,d1ea454e-594c-4746-9de7-e813377ff095")
  .header("Host", "upload.goskip.com:8080")
  .header("accept-encoding", "gzip, deflate")
  .header("content-length", "407")
  .header("Connection", "keep-alive")
  .header("cache-control", "no-cache")
  .body("------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"path\"; filename=\"<<file_name>>\"\r\nContent-Type: application/vnd.novadigm.ext\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"data\"\r\n\r\n[{\"file_key\": \"path\", \"store_id\":<<store_id>>}]\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--")
  .asString();

PHP с использованием HttpRequest:

<?php

$request = new HttpRequest();
$request->setUrl('https://upload.goskip.com:8080/v2/backoffice/files/pricebook');
$request->setMethod(HTTP_METH_POST);

$request->setQueryData(array(
  'type' => 'json'
));

$request->setHeaders(array(
  'cache-control' => 'no-cache',
  'Connection' => 'keep-alive',
  'content-length' => '407',
  'accept-encoding' => 'gzip, deflate',
  'Host' => 'upload.goskip.com:8080',
  'Postman-Token' => 'de9932d2-bda0-4df6-8a9a-e2d4f74c2048,c7acbc0a-6450-43d1-b4ca-bc2a56cebc4e',
  'Cache-Control' => 'no-cache',
  'Accept' => '*/*',
  'User-Agent' => 'PostmanRuntime/7.13.0',
  'Content-Type' => 'application/x-www-form-urlencoded',
  'Authorization' => 'Bearer <<api_user_token>>',
  'content-type' => 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'
));

$request->setBody('------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name = "path"; filename = "<<file_name>>"
Content-Type: application/vnd.novadigm.ext


------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name = "data"

[{"file_key": "path", "store_id"::<<store_id>>}]
------WebKitFormBoundary7MA4YWxkTrZu0gW--');

try {
  $response = $request->send();

  echo $response->getBody();
} catch (HttpException $ex) {
  echo $ex;
}

Вот что я пробовал в Indy. Я вставил файл JSON в памятку в форме:

function TFormJsonWrite.HTTPPost: String;
var
  JsonToSend : TStringStream;
  Response: String;
  IdHTTP1: TIdHTTP;
  IdSSLIOHandlerSocketOpenSSL2: TIdSSLIOHandlerSocketOpenSSL;
begin
  IdSSLIOHandlerSocketOpenSSL2 := TIdSSLIOHandlerSocketOpenSSL.Create;
  IdHTTP1 := TIdHTTP.Create;
  IdHTTP1.Request.CharSet := 'utf-8';

  JsonToSend := TStringStream.Create(memolog.text, TEncoding.UTF8);
  IdHTTP1.Request.ContentDisposition := 'form-data; name=[{"file_key":''' + memolog.text + ''', "store_id"::001}]------WebKitFormBoundary7MA4YWxkTrZu0gW--';
  IdHTTP1.Request.UserAgent := 'Mozilla/3.0 (compatible; Indy Library)';
  IdHTTP1.Request.ContentType := 'multipart/form-data';
  IdHTTP1.Request.Accept := '*/*';
  IdHTTP1.Request.AcceptEncoding := 'gzip,deflate';
  IdHTTP1.Request.ContentLength := -1;
  IdHTTP1.Request.CacheControl := 'no-cache';
  IdHTTP1.Request.Connection := 'keep-alive';
  IdHTTP1.Request.BasicAuthentication := false;
  IdHTTP1.Request.Host := 'upload.goskip.com:8080';
  IdHTTP1.Request.CustomHeaders.Clear;
  IdHTTP1.Request.CustomHeaders.Values['Authorization'] := 'Bearer eyJhbGc.......................';
  IdHTTP1.Request.CustomHeaders.Values['Postman-Token'] := 'de9932d2-bda0-4df6-8a9a-e2d4f74c2048,d59a9861-1a65-471a-8ada-6e025e985dca';
  IdHTTP1.ReadTimeout := 50000;
  IdSSLIOHandlerSocketOpenSSL2.SSLOptions.Method := sslvTLSv1_2;
  IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL2;
  Response := IdHTTP1.Post('https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json', JsonToSend);
  HTTPPost := Response;
  JsonToSend.Free;
  IdHTTP1.Free;
end;

завиток

Kim@KIMNEW MINGW64 ~
$ curl -F "file=@"C:/mydata/Items-114946.json -H "authorization:Bearer eyJ........................." https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json&store_id=001
[1] 13296
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
Kim@KIMNEW MINGW64 ~
100  951k  100    53  100  951k     53   968k  0:00:01 --:--:--  0:00:01  967k{"message":"#/data: null value where array expected"}

Они обновляют все свои конечные точки, это последнее, что я получил.

curl -X POST   'https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json'   -H 'Authorization: Bearer <api_user_token>' -F path=@<path_to_first_file>   -F path1=@<path_to_second_file>   -F 'data=[{"file_key":"path","store_id":<store_id_for_first_file>},{"file_key":"path1","store_id":<store_id_for_second_file>}]'
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
115
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш код Delphi неверен.

Вы устанавливаете неправильный заголовок Content-Disposition на неправильное значение.

Вы публикуете данные JSON как есть, вообще не помещая их в MIME.

НЕ устанавливайте свойство TIdHTTP.Request.AcceptEncoding вручную. Вы не настраиваете TIdHTTP для включения поддержки сжатия, но вы даете серверу разрешение отправлять сжатые ответы, которые TIdHTTP не смогут распаковать для вас.

Способ правильный отправить запрос multipart/form-data с использованием TIdHTTP заключается в использовании перегруженного метода Post(), который принимает TIdMultipartFormDataStream в качестве входных данных, например:

function TFormJsonWrite.HTTPPost: String;
var
  JsonToSend : String;
  IdHTTP1: TIdHTTP;
  IdSSLIOHandlerSocketOpenSSL2: TIdSSLIOHandlerSocketOpenSSL;
  PostData: TIdMultipartFormDataStream;
begin
  JsonToSend := '[{"file_key": "path", "store_id": <<store_id>>}]';

  IdHTTP1 := TIdHTTP.Create;
  try
    IdHTTP1.ReadTimeout := 50000;

    IdHTTP1.Request.BasicAuthentication := false;
    IdHTTP1.Request.CustomHeaders.Values['Authorization'] := 'Bearer ...';
    IdHTTP1.Request.CustomHeaders.Values['Postman-Token'] := '...';

    IdSSLIOHandlerSocketOpenSSL2 := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP1);
    IdSSLIOHandlerSocketOpenSSL2.SSLOptions.Method := sslvTLSv1_2;
    IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL2;

    PostData := TIdMultipartFormDataStream.Create;
    try
      PostData.AddFormField('path', '', '', 'application/vnd.novadigm.ext').FileName := '<<file_name>>';
      PostData.AddFormField('data', JsonToSend, 'utf-8', 'application/json').ContentTransfer := '8bit';

      Result := IdHTTP1.Post('https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json', PostData);
    finally
      PostData.Free;
    end;
  finally
    IdHTTP1.Free;
  end;
end;

Если случайно у сервера возникают проблемы с тем, как TIdMultipartFormDataStream форматирует данные MIME (т. е. если сервер отклоняет заголовки MIME Content-Type и/или Content-Transfer-Encoding, которые генерируются TIdMultipartFormDataStream для каждого поля MIME, поскольку он еще не соответствует RFC 7578, который используется HTML5), вы можете отформатировать данные MIME вручную, чтобы они точно соответствовали примерам SKIP, например:

function TFormJsonWrite.HTTPPost: String;
var
  JsonToSend : String;
  IdHTTP1: TIdHTTP;
  IdSSLIOHandlerSocketOpenSSL2: TIdSSLIOHandlerSocketOpenSSL;
  PostData: TStringStream;
begin
  JsonToSend := '[{"file_key": "path", "store_id": <<store_id>>}]';

  IdHTTP1 := TIdHTTP.Create;
  try
    IdHTTP1.ReadTimeout := 50000;

    IdHTTP1.Request.ContentType := 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW';
    IdHTTP1.Request.BasicAuthentication := false;
    IdHTTP1.Request.CustomHeaders.Values['Authorization'] := 'Bearer ...';
    IdHTTP1.Request.CustomHeaders.Values['Postman-Token'] := '...';

    IdSSLIOHandlerSocketOpenSSL2 := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP1);
    IdSSLIOHandlerSocketOpenSSL2.SSLOptions.Method := sslvTLSv1_2;
    IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL2;

    PostData := TStringStream.Create(
      '------WebKitFormBoundary7MA4YWxkTrZu0gW' + EOL +
      'Content-Disposition: form-data; name = "path"; filename = "<<file_name>>"' + EOL +
      'Content-Type: application/vnd.novadigm.ext' + EOL +
      EOL +
      EOL +
      '------WebKitFormBoundary7MA4YWxkTrZu0gW' + EOL +
      'Content-Disposition: form-data; name = "data"' + EOL +
      EOL +
      JsonToSend + EOL +
      '------WebKitFormBoundary7MA4YWxkTrZu0gW--',
      TEncoding.UTF8
    );
    try
      Result := IdHTTP1.Post('https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json', PostData);
    finally
      PostData.Free;
    end;
  finally
    IdHTTP1.Free;
  end;
end;

ОБНОВИТЬ: на основе новых команд curl, которые вы предоставили, исходные примеры, которые вы показали, не соответствуют командам. Вместо этого попробуйте что-то вроде этого:

function TFormJsonWrite.HTTPPost: String;
var
  JsonToSend : String;
  IdHTTP1: TIdHTTP;
  IdSSLIOHandlerSocketOpenSSL2: TIdSSLIOHandlerSocketOpenSSL;
  PostData: TIdMultipartFormDataStream;
begin
  JsonToSend := '[{"file_key": "path", "store_id": <<store_id>>}]';

  IdHTTP1 := TIdHTTP.Create;
  try
    IdHTTP1.ReadTimeout := 50000;

    IdHTTP1.Request.BasicAuthentication := false;
    IdHTTP1.Request.CustomHeaders.Values['Authorization'] := 'Bearer ...';
    IdHTTP1.Request.CustomHeaders.Values['Postman-Token'] := '...';

    IdSSLIOHandlerSocketOpenSSL2 := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP1);
    IdSSLIOHandlerSocketOpenSSL2.SSLOptions.Method := sslvTLSv1_2;
    IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL2;

    PostData := TIdMultipartFormDataStream.Create;
    try
      PostData.AddFile('path', '<<path_to_file>>').ContentTransfer := 'binary';
      PostData.AddFormField('data', JsonToSend, 'utf-8', 'application/json').ContentTransfer := '8bit';

      Result := IdHTTP1.Post('https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json', PostData);
    finally
      PostData.Free;
    end;
  finally
    IdHTTP1.Free;
  end;
end;

Или:

function TFormJsonWrite.HTTPPost: String;
var
  JsonToSend : String;
  IdHTTP1: TIdHTTP;
  IdSSLIOHandlerSocketOpenSSL2: TIdSSLIOHandlerSocketOpenSSL;
  PostData: TMemoryStream;
  FS: TIdReadFileExclusiveStream;
begin
  JsonToSend := '[{"file_key": "path", "store_id": <<store_id>>}]';

  IdHTTP1 := TIdHTTP.Create;
  try
    IdHTTP1.ReadTimeout := 50000;

    IdHTTP1.Request.ContentType := 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW';
    IdHTTP1.Request.BasicAuthentication := false;
    IdHTTP1.Request.CustomHeaders.Values['Authorization'] := 'Bearer ...';
    IdHTTP1.Request.CustomHeaders.Values['Postman-Token'] := '...';

    IdSSLIOHandlerSocketOpenSSL2 := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP1);
    IdSSLIOHandlerSocketOpenSSL2.SSLOptions.Method := sslvTLSv1_2;
    IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL2;

    PostData := TMemoryStream.Create;
    try
      WriteStringToStream(PostData, '------WebKitFormBoundary7MA4YWxkTrZu0gW' + EOL);
      WriteStringToStream(PostData, 'Content-Disposition: form-data; name = "path"; filename = "<<file_name>>"' + EOL);
      WriteStringToStream(PostData, 'Content-Type: ' + GetMIMETypeFromFile('<<file_name>>') + EOL);
      WriteStringToStream(PostData, 'Content-Transfer-Encoding: binary' + EOL);
      WriteStringToStream(PostData, EOL);

      FS := TIdReadFileExclusiveStream.Create('<<path_to_file>>', fmOpenRead or fmShareDenyWrite);
      try
        PostData.CopyFrom(FS, 0);
      finally
        FS.Free;
      end;

      WriteStringToStream(PostData, EOL);
      WriteStringToStream(PostData, '------WebKitFormBoundary7MA4YWxkTrZu0gW' + EOL);
      WriteStringToStream(PostData, 'Content-Disposition: form-data; name = "data"' + EOL);
      WriteStringToStream(PostData, EOL);
      WriteStringToStream(PostData, JsonToSend + EOL, IndyTextEncoding_UTF8);
      WriteStringToStream(PostData, '------WebKitFormBoundary7MA4YWxkTrZu0gW--');

      PostData.Position := 0;

      Result := IdHTTP1.Post('https://upload.goskip.com:8080/v2/backoffice/files/pricebook?type=json', PostData);
    finally
      PostData.Free;
    end;
  finally
    IdHTTP1.Free;
  end;
end;

Спасибо, Реми. Я пробовал оба. Первый выдает мне исключение EidHTTPProtocolException с сообщением «HTTP/1.1 403 Forbidden. Второй я предположил, что EOL был строкой с EOL := '\r\n'. Я вижу, что в TStringStreamCreate есть имя файла = "myjson.json", но я не понимаю, как он получает содержимое файла в TStringStream. Я получаю внутренний сервер 500

Kim HJ 01.06.2019 00:21

Я добавил завиток, и похоже, он работает, за исключением ошибки с файлом json, который составляет 952 КБ.

Kim HJ 01.06.2019 01:57

Я добавил новый завиток.

Kim HJ 01.06.2019 02:15

Я только что протестировал новый curl с Git Bash, и он работает, но я все еще получаю 403, используя Delphi Indy. Любые предложения, спасибо.

Kim HJ 01.06.2019 02:46
EOL определяется в единицах IdGlobal Инди. Да, это \r\n. Ошибка HTTP 403 означает, что ваша аутентификация неверна, либо из-за того, что токен OAuth устарел или иным образом недействителен, либо, возможно, вы пропустили шаг в процессе аутентификации, такой как запрос токена OAuth или получение файлов cookie, которые необходимо отправить обратно, и т. д. Что касается команд curl, я обновил свой ответ, чтобы отразить их. Если вы все еще получаете ошибки, вам нужно зафиксировать фактические HTTP-запросы, которые отправляет curl, и сравнить их с запросами, которые отправляет TIdHTTP.
Remy Lebeau 01.06.2019 05:22

Спасибо, Реми, TIdMultipartFormDataStream работает безупречно. Я ценю ваше время, чтобы помочь мне, второй раз, когда вы спасли меня за последний месяц.

Kim HJ 03.06.2019 23:27

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