Search code examples
indydelphi-xe6

Upload to Skip Web Server


I'm trying to upload a JSON file to the SKIP (getskip.com) web server and I am getting socket error 10054. They are not very good at giving samples of how, since they think everyone is using cUrl, but we are still using Delphi XE6 with Indy.

Here is what they provided.

For Java using 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 using 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 using 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;
}

Here is what I tried in Indy. I pasted the JSON file in to a Memo on the Form:

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;

curl

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"}

They updating all their endpoints this is the latest I got.

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>}]'

Solution

  • Your Delphi code is all wrong.

    You are setting the wrong Content-Disposition header to the wrong value.

    You are posting the JSON data as-is without wrapping it inside of MIME at all.

    DO NOT set the TIdHTTP.Request.AcceptEncoding property manually. You are not setting up the TIdHTTP to enable compression support, but you are giving the server permission to send compressed responses, which TIdHTTP will not be able to decompress for you.

    The correct way to send a multipart/form-data request using TIdHTTP is to use the overloaded Post() method that takes a TIdMultipartFormDataStream as input, like this:

    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;
    

    If, by chance, the server has issues with how TIdMultipartFormDataStream formats the MIME data (ie, if the server rejects the MIME Content-Type and/or Content-Transfer-Encoding headers that are generated by TIdMultipartFormDataStream for each MIME field, as it does not yet conform to RFC 7578, which is used by HTML5), you can format the MIME data manually to match SKIP's examples exactly, like this:

    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;
    

    UPDATE: based on the new curl commands you have provided, the original examples you showed do not match the commands. Try something more like this instead:

    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;
    

    Or:

    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;