为什么以下请求会返回异常:响应内容不是有效的 JSON

问题描述 投票:0回答:1

我正在使用 Delphi 10.2 和

TRESTRequest
TRESTClient
TRESTResponse
组件以及
REST.Response.Adapter
REST.Client
System.JSON
REST.Types
库来进行 API 调用。

第一个 API 调用需要用户名和密码,并返回带有两个键值对的数据对象:

  1. token
    :表示 JWT(JSON Web 令牌)的字符串。

  2. expiresIn
    也是一个字符串,指示令牌过期之前的持续时间,例如30分钟。

procedure TfrmMain.btnAuth2Click(Sender: TObject);
var
  JsonRequest: TJSONObject;
  ResponseJson: TJSONObject;
  DataJson: TJSONObject;
  TestUserName: string;
  TestPassword: string;
  JsonBody: string;
  I: Integer;
begin
  TestUserName := frmLogIn.UserName;
  TestPassword := frmLogIn.Password;
  // Set up the Base URL
  RESTClient1.BaseURL := '';
  RESTClient1.BaseURL := 'https://dev-api.nameofwebsite.com/function';
  RESTRequest1.Resource := '/auth/login';
    
  // Set request headers
  RESTRequest1.Method := rmPOST;
  RESTRequest1.Params.Clear; // Clear any existing parameters
  RESTRequest1.Params.AddHeader('accept', 'application/json');
    
  // Prepare the JSON request body.
  JsonRequest := TJSONObject.Create;
    
  JsonRequest.AddPair('email', TJSONString.Create(TestUserName));
  JsonRequest.AddPair('password', TJSONString.Create(TestPassword));
    
  // Convert JSON request body to string
  JsonBody := JsonRequest.ToString;
    
  // Save JSON body to file for inspection
  SaveJsonBodyToFile('AuthRequest_body.json', JsonBody);
    
  // Set the JSON request body
  RESTRequest1.AddBody(JsonBody, ctAPPLICATION_JSON);
      
  // Used to strip out and inspect the header and body
  mmoDisplay.Lines.Add('');
  mmoDisplay.Lines.Add('Request Method: ' + RESTRequestMethodToString
    (RESTRequest1.Method));
  mmoDisplay.Lines.Add('Request URL: ' + RESTRequest1.Resource);
  mmoDisplay.Lines.Add('Request Headers:');
  for I := 0 to RESTRequest1.Params.Count - 1 do
  begin
    if (RESTRequest1.Params[I].Kind = pkHTTPHEADER) OR
      (RESTRequest1.Params[I].Kind = pkURLSEGMENT) OR
      (RESTRequest1.Params[I].Kind = pkREQUESTBODY) then
    begin
      mmoDisplay.Lines.Add(RESTRequest1.Params[I].Name + ': ' +
        RESTRequest1.Params[I].Value);
    end;
  end;
    
  RESTRequest1.Execute;
    
  // Log the response content
  mmoDisplay.Lines.Add('Response Content: ' + RESTResponse1.Content);
    
  // Handle the response
  if RESTResponse1.StatusCode = 200 then
  begin
    ResponseJson := TJSONObject.ParseJSONValue(RESTResponse1.Content)
      as TJSONObject;
    try
      if Assigned(ResponseJson) then
      begin
        DataJson := ResponseJson.GetValue('data') as TJSONObject;
        if Assigned(DataJson) then
        begin
          mmoDisplay.Lines.Add('Token: ' + DataJson.GetValue('token').Value);
          OriginalAuthToken := DataJson.GetValue('token').Value;
        end;
      end;
    finally
      ResponseJson.Free;
    end;
  end
  else
  begin
    // Handle error response
    raise Exception.Create('Error: ' + RESTResponse1.StatusText);
  end;
  mmoDisplay.Lines.Add('Server Response: ' + IntToStr(RESTResponse1.StatusCode)
    + ' ' + RESTResponse1.StatusText);
end;

然后,我对同一服务器进行另一个 API 调用,其中的想法是传递第一个 API 调用中的

token
以及从适用于 Android 手机的 Microsoft Authenticator 应用程序生成的 OTP:

procedure TfrmMain.btnVerifyOTPClick(Sender: TObject);
var
  OTPJsonRequest: TJSONObject;
  ResponseJson: TJSONObject;
  DataJson: TJSONObject;
  TestOTP: string;
  kjd: string;
  KJsonBody: string;
  I: Integer;
begin
  frmotp.ShowModal;
  TestOTP := frmotp.OTP;
  // Set up the Base URL
  RESTClient1.BaseURL := 'https://dev-api.nameofwebsite.com/function';
    
  // Set the resource path
  RESTRequest1.Resource := '/auth/verify-otp';
    
  // Display the auth token.
  mmoDisplay.Lines.Add('');
  mmoDisplay.Lines.Add('OTP: ' + TestOTP);
  mmoDisplay.Lines.Add('Auth Token to be submitted: ' + OriginalAuthToken);
    
  // // Set request headers
  RESTRequest1.Method := rmPOST;
  RESTRequest1.Params.Clear; // Clear any existing parameters
  RESTRequest1.Params.AddHeader('accept', 'application/json');
  RESTRequest1.Params.AddHeader('Authorization', OriginalAuthToken);
  // Prepare the JSON request body
  OTPJsonRequest := TJSONObject.Create;
  OTPJsonRequest.AddPair('otp', TJSONString.Create(TestOTP));
  // OTPJsonRequest.AddPair('otp', TJSONNumber.Create(TestOTP));
    
  // Convert JSON request body to string
  KJsonBody := OTPJsonRequest.ToString;
    
  // Save JSON body to file for inspection
  SaveJsonBodyToFile('request_body.json', KJsonBody);
    
  RESTRequest1.AddBody(KJsonBody, ctAPPLICATION_JSON);
    
  // Before executing the request
  // Log the complete request
  mmoDisplay.Lines.Add('');
  mmoDisplay.Lines.Add('Request Method: ' + RESTRequestMethodToString
    (RESTRequest1.Method));
  mmoDisplay.Lines.Add('Request URL: ' + RESTRequest1.Resource);
  mmoDisplay.Lines.Add('Request Headers:');
  for I := 0 to RESTRequest1.Params.Count - 1 do
  begin
    mmoDisplay.Lines.Add(RESTRequest1.Params[I].Name + ': ' +
      RESTRequest1.Params[I].Value);
  end;
  // Execute the request
  try
    RESTRequest1.Execute;
    // Log the response content
    mmoDisplay.Lines.Add('Response Content: ' + RESTResponse1.ContentEncoding +
      ' ' + RESTResponse1.Content);
  except
    on E: Exception do
    begin
      // Log any exceptions that occur during the request
      mmoDisplay.Lines.Add('Exception: ' + E.Message);
    end;
    
  end;
    
  // Handle the response
  if RESTResponse1.StatusCode = 200 then
  begin
    ResponseJson := TJSONObject.ParseJSONValue(RESTResponse1.Content)
      as TJSONObject;
    try
      if Assigned(ResponseJson) then
      begin
        DataJson := ResponseJson.GetValue('data') as TJSONObject;
        if Assigned(DataJson) then
        begin
          mmoDisplay.Lines.Add('Token: ' + DataJson.GetValue('token').Value);
          OriginalAuthToken := DataJson.GetValue('token').Value;
        end;
      end;
    finally
      OTPJsonRequest.Free;
    end;
  end
  else
  begin
    // Handle error response
    raise Exception.Create('Error: ' + RESTResponse1.StatusText);
  end;
  mmoDisplay.Lines.Add('Server Response: ' + IntToStr(RESTResponse1.StatusCode)
    + ' ' + RESTResponse1.StatusText);
end;

我已经在两个不同的 API 测试环境中测试了 API 调用,例如Swagger 和 Postman,这两个 API 都可以工作。

我目前无法让第二个 API 在 Delphi 中工作,因为它在

RESTRequest1.Execute;
上生成异常,例如:

异常:响应内容不是有效的 JSON

对于我在第二次 API 调用中出错的任何想法,我将不胜感激。

这是我第一次尝试从 Delphi 编写 API 调用,因此如果有人可以推荐任何资源,了解一下也很好。

json delphi
1个回答
0
投票

非常感谢@RemyLebeau 的回复。 在身份验证/登录时,我得到以下合法回复:

{"data":{"token":"Bearer 
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOiI2NWJhMWI1ZTY4NzY5NTMxYTkzZTFiMTgiLCJpYXQiOjE3MTUyNDc5ODYsImV4cCI6MTcxNTI0OTc4Nn0.JgkfN4var05APlSga3NzggLJ5rX1AxrBj0QZtHSs0cw",
"expiresIn":"30m"}}

在第二个 verify-otp API 调用中,我在将授权添加到标头时遇到问题。

本来我是这样做的:

RESTRequest1.Params.AddHeader('Authorization', 'Bearer ' + OriginalAuthToken);

删除标题内容,我可以看到“Bearer”关键字被添加了两次,例如

Authorization: Bearer Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOiI2NWJhMWI1ZTY4NzY5NTMxYTkzZTFiMTgiLCJpYXQiOjE3MTUyNDgyMjIsImV4cCI6MTcxNTI1MDAyMn0.uhwG_coQ4EBYzhymwVM1X7KPHCQid0Y_qYP4-eHjgLs
body219E405A2A624330AA4C092D6892C1A3: {"otp":"683937"}

似乎有东西在标题中添加了关键字“Bearer”,所以我将代码更改为:

RESTRequest1.Params.AddHeader('Authorization', OriginalAuthToken);

这似乎可以正确构造标题,例如

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOiI2NWJhMWI1ZTY4NzY5NTMxYTkzZTFiMTgiLCJpYXQiOjE3MTUyNDgzNDgsImV4cCI6MTcxNTI1MDE0OH0.jlVONiopNLNk6avIqgxn6f7oTsftMAeAECBLNC7VikQ
body48EED8480CA643BBA56411C646D76D90: {"otp":"134513"}

然而,在与网络开发人员沟通后,他看到的是 Bearer 和 Token 值之间的“%20”。

授权:'承载%20eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOiI2NWJhMWI1ZTY4NzY5NTMxYTkzZTFiMTgiLCJpYXQiOjE3MTUyNDgzNDgsImV4cCI6MTcxNTI1MDE0OH0.jlVO NiopNLNk6avIqgxn6f7oTsftMAeAECBLNC7VikQ

有什么方法可以确保标头不包含这个“%20”吗?

© www.soinside.com 2019 - 2024. All rights reserved.