0

I have tested POST function in PostMan to do POST function with body parameters as below:

enter image description here

enter image description here

Here is eBay's document for this function:

  HTTP method:   POST
  URL (Sandbox): https://api.sandbox.ebay.com/identity/v1/oauth2/token

  HTTP headers:
    Content-Type = application/x-www-form-urlencoded
    Authorization = Basic <B64-encoded-oauth-credentials>

  Request body:
    grant_type=authorization_code
    code=<authorization-code-value>
    redirect_uri=<RuName-value>

My first attempt was as follow:

function udxEBayExchangeAuthCodeForUserToken(AAuthCode: String; AIsProduction: Boolean): String;
var
  xRequestBody: TStringStream;
begin
  with objHTTP.Request do begin
    CustomHeaders.Clear;
    ContentType := 'application/x-www-form-urlencoded';
    CustomHeaders.Values['Authorization'] := 'Basic ' + 'V2VpbmluZ0MtV2VpbmluZ0M';
  end;

  xRequestBody := TStringStream.Create('grant_type=' + 'authorization_code' + ','
                                     + 'code=' + 'v%5E1.1%23i%5E1%23f%5E0%23r%5E1%23I%5E3%23p%5E3' + ','
                                     + 'redirect_uri=' + 'myredirecturlnameinsandbox',
                                        TEncoding.UTF8);

  try
    try
      Result := objHTTP.Post('https://api.sandbox.ebay.com/identity/v1/oauth2/token', xRequestBody);
    except
      on E: Exception do
        ShowMessage('Error on request: ' + #13#10 + e.Message);
    end;
  finally
    xRequestBody.Free;
  end;
end;

Second attempt tried with below code for Body

  xRequestBody := TStringStream.Create('grant_type=' + 'authorization_code' + '&'
                                     + 'code=' + AAuthCode + '&'
                                     + 'redirect_uri=' + gsRuName,
                                        TEncoding.UTF8);

Both attempts give HTTP/1.1 400 Bad Request.

I have done some searching in Stack Overflow, and this is the closest question. The only different part is body of POST.

IdHTTP how to send raw body

Can anyone please advise me what is correct way to assign POST body part? Thanks.

Peter Wolf
  • 3,700
  • 1
  • 15
  • 30
James
  • 71
  • 1
  • 8
  • There are several thing in the code that you do wrong. Besides that, you say that you're getting `400 Bad Request`, but that's the same as in Postman. So what is the real question here? How to POST via Postman? How to POST via Indy? How to use eBay API? Anyhow, I pasted your sample code into a plain VCL application in Delphi 10.2 and I got `401 Unathorized`. Is there something you didn't show us? – Peter Wolf Dec 10 '19 at 16:50
  • @PeterWolf Thank you for your help. Three parameters (['Authorization'], 'code=', 'redirect_uri=') were not pasted completely in sample code. This POST call is following with another GET call to get the value of code inside POST body, which expires in 299 seconds after it is granted from eBay. With your comments, now I found that Postman has status "400 Bad Request". Let me do some more tests in Postman. – James Dec 10 '19 at 23:05
  • One question: *{ "error": "invalid_grant", "error_description": "the provided authorization grant code is invalid or was issued to another client" }* This message is shown in Postman and I thought it is feedback from server. If so, does this mean that POST call is ok but with invalid authorization code? Thanks. – James Dec 10 '19 at 23:34
  • Yes, your POST request made it to the server and it responded with the JSON content. The server gives hint why the request can't be succesfully served (401 - authorization issue, 400 - malformed request). In my first comment I wrote you have some issues in your code. See Remy's answer, which pretty much sums them up. – Peter Wolf Dec 10 '19 at 23:42
  • @PeterWolf Thank you for your quick reply. I am doing more tests with Remy's codes. – James Dec 11 '19 at 00:52
  • When server gave both 401 - authorization issue and 400 - malformed request error, they stand for different mistakes inside POST call. I don't know which one is the real reason for unsuccessful calling. Is there fucntion inside idHTTP to get same feedback information from server like POSTMAN? Thanks. – James Dec 11 '19 at 01:10

3 Answers3

2

The preferred way to send an application/x-www-form-urlencoded request with TIdHTTP is to use the overloaded TIdHTTP.Post() method that takes a TStrings as input. You are not sending your TStringStream data in the proper application/x-www-form-urlencoded format.

You don't need to use the TIdHTTP.Request.CustomHeaders property to setup Basic authorization. TIdHTTP has built-in support for Basic, simply use the TIdHTTP.Request.UserName and TIdHTTP.Request.Password properties as needed, and set the TIdHTTP.Request.BasicAuthentication property to true.

Try this instead:

function udxEBayExchangeAuthCodeForUserToken(AAuthCode: String; AIsProduction: Boolean): String;
var
  xRequestBody: TStringList;
begin
  with objHTTP.Request do
  begin
    Clear;
    ContentType := 'application/x-www-form-urlencoded';
    BasicAuthentication := True;
    UserName := ...;
    Password := ...;
  end;

  xRequestBody := TStringList.Create;
  try
    xRequestBody.Add('grant_type=' + 'authorization_code');
    xRequestBody.Add('code=' + AAuthCode);
    xRequestBody.Add('redirect_uri=' + 'myredirecturlnameinsandbox');

    try
      Result := objHTTP.Post('https://api.sandbox.ebay.com/identity/v1/oauth2/token', xRequestBody);
    except
      on E: Exception do
        ShowMessage('Error on request: ' + #13#10 + e.Message);
    end;
  finally
    xRequestBody.Free;
  end;
end;

If you want to send your own TStringStream, try this instead:

function udxEBayExchangeAuthCodeForUserToken(AAuthCode: String; AIsProduction: Boolean): String;
var
  xRequestBody: TStringStream;
begin
  with objHTTP.Request do
  begin
    Clear;
    ContentType := 'application/x-www-form-urlencoded';
    BasicAuthentication := True;
    UserName := ...;
    Password := ...;
  end;

  xRequestBody := TStringStream.Create('grant_type=' + 'authorization_code' + '&'
                                     + 'code=' + TIdURI.ParamsEncode(AAuthCode){'v%5E1.1%23i%5E1%23f%5E0%23r%5E1%23I%5E3%23p%5E3'} + '&'
                                     + 'redirect_uri=' + 'myredirecturlnameinsandbox',
                                        TEncoding.UTF8);
  try
    try
      xRequestBody.Position := 0;

      Result := objHTTP.Post('https://api.sandbox.ebay.com/identity/v1/oauth2/token', xRequestBody);
    except
      on E: Exception do
        ShowMessage('Error on request: ' + #13#10 + e.Message);
    end;
  finally
    xRequestBody.Free;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you for your detail explanation and sample code. I did try both and same error is still happened. One thing I didn't change is objHTTP.Request.UserName/Password. There is only Authorization used for eBay API call and I think username is already encoded inside Authorization code. – James Dec 11 '19 at 00:27
  • I did more tests in Postman with fresh AuthCode, with valid/invalid ['Authorization'], server will give different error: invalid_grant (400 Bad Request)/invalid_client (401 Unauthorized). Tried same inside my application, error codes are same as Postman: 400 vs 401. It looks POST call is pasted to server properly but there is something else not right. It might be 'code=' inside POST call's body or others. Keep testing with Remy's new code and looking for further advice. Thanks. – James Dec 11 '19 at 00:57
0

I was troubled by this problem, but the answer didn't work very much. Later, I used packet capturing to see the actual content sent. For friends with the same problems.

function TFormMain.init_web_account(): Boolean;
var
  strTmp: Ansistring;
  PostStringData: TstringStream;
  idhttp1: Tidhttp;
  json, json_array1: TQjson;
  itype_version: integer;
  ifunction_version: integer;
  s: string;
  url: ansistring;
  idMultiPartFormDataStream:TIdMultiPartFormDataStream;
begin
  try
    result := False;
    idhttp1 := TIdHTTP.Create();
    PostStringData := TStringStream.Create;



    //init_string  http://x.x.x.x:8000/user/key?ke1=v1&k2=v2
    strTmp := //
      'strdogsoftid=' + edtDogNumber.Text + //
      '&chipid=' + '' + //
      '&status=' + '0' +  //
      '&reg_user_name=' + edtRegName.Text + //
      '&reg_init_date=' + formatdatetime('yyyy-mm-dd', dtpRegCodeGenDate.Date) + //
      '&lease_expiration=' + formatdatetime('yyyy-mm-dd', dtp_lease_expiration.Date) + //
      '&renew_last_date=' + '' + //
      '&mobile=' + '' + //
      '&weixin=' + '' + //
      '&memo=' + '' + //
      '&function_version=' + IntToStr(rgVersion.ItemIndex) + //
      '&type_version=' + IntToStr(itype_version); //

    url := 'http://x.x.x.x:8000/user/' + edtDogNumber.Text;
    PostStringData.Clear;
    PostStringData.WriteString(strTmp);


    try

      idhttp1.Request.ContentType := 'application/x-www-form-urlencoded';



      s := idhttp1.Post(url, PostStringData); 
      result := True;


    except
      on E: Exception do
      begin
        showmessage(s);
        result := False; 
        exit;
      end;

    end;
  finally
    PostStringData.Free;
    idhttp1.Free;

  end;
end;
  • Thank you very much for your new code, dreamnyj. I would like to know how to do packet capturing to see actual content idHTTP sent. – James Feb 25 '20 at 10:42
  • First, we use postman to construct a standard request, and then we use packet capturing software to capture the actual content sent. The capture software sets our target address to achieve filtering. For specific package capturing software, please search by yourself. – hero cattle Mar 05 '20 at 07:06
0

If you use TNetHTTPClient you can use the "System.Net.Mime" unit to manage the multipart form data by HTTP Post and manage request too.

I use Delphi 10.4.2

Ivan Revelli
  • 363
  • 4
  • 8