Delphi - TIdHTTP 在发布数据时冻结

Delphi - TIdHTTP freezes when POSTing data

我在使用 Indy 的 TIdHTTP 组件开发 HTTP 客户端时遇到问题。我正在使用 Indy 10.5.9.0.

当我调用 TIdHTTP.DoRequest() 方法时,组件冻结并引发异常:

Connection closed gracefully

我已经尝试使用 TIdHTTP.Post() 方法,但出现了同样的问题。

使用 TIdHTTP.Get() 方法不会出现此问题。

这是我的代码:

try
  jsonLatestFirmware := TJSONObject.ParseJSONValue(httpClient.Get(strGetLatest)) as TJSONObject;
  try
    srv := 'http://' + currentAlive.IP + ':9335/upgrade?command=start';

    sList := TStringList.Create;
    sList.Text := jsonLatestFirmware.get('data').JsonValue.ToString;

    fileName := ExtractFilePath(Application.ExeName) + 'finals\' + currentMACQR + '-' + currentAlive.IP + '.json';

    sList.SaveToFile(fileName);

    JsonRetorno := TStringStream.Create('');
    try
      JsonToSend := TStringStream.Create;

      WriteStringToStream(JsonToSend,TIdHTTPAccess(httpClient).SetRequestParams(sList, TEncoding.UTF8));
      JsonToSend.Position := 0;
      TIdHTTPAccess(httpClient).DoRequest('POST',srv,JsonToSend,jsonRetorno,[]); //component freezes here...

      strRetorno := TStringList.Create();
      strRetorno.LoadFromStream(JsonRetorno);

      lblInformacao.Visible := True;

      ShowMessage(strRetorno.Text);

    except
      on E: Exception do
        ShowMessage('Error on request: '#13#10 + E.Message);
    end;

  finally
    sList.Free;
    JsonRetorno.Free;
    JsonToSend.Free;
    jsonResponse.Free;
  end;
except
  on E: Exception do
    ShowMessage('There are no firmwares available for this product.');
end;

谁能帮我解决这个问题?

I'm using Indy 10.5.9.0.

那是一个非常古老和过时的版本。在撰写本文时,最新版本为 10.6.2.5455。 Please upgrade,因为自 10.5.9 以来对 Indy 进行了大量更改和修复。

When I call the TIdHTTP.Post() method, the component freezes, and raises an exception:

首先,你不是在调用TIdHTTP.Post(),你是在调用TIdHTTP.DoRequest()。您应该改为调用 TIdHTTP.Post(),它会在内部为您调用 TIdHTTP.DoRequest()

httpClient.Post(srv, JsonToSend, jsonRetorno);

其次,Post() 不能同时冻结和引发异常。到底发生了什么?您 post 的 JSON 有多大?在引发异常之前服务器是否发送任何类型的响应?

第三,你绝对根本不应该调用TIdHTTP.SetRequestParams()。当 TIdHTTP.Post()application/x-www-webform-urlencoded 格式提交 HTML 网络表单时,该方法供 TStrings 重载版本内部使用。在这种情况下,这不是您所需要的。您需要按原样 post 您的 JSON,而不是对其进行 webform 编码。

Indy 使用阻塞套接字。 Indy 中的大多数操作都是同步的。 TIdHTTP.Post() 也不例外。它会阻塞调用线程,直到 HTTP post 完成。如果它冻结,则意味着 post 需要很长时间才能发送,或者服务器没有发回有效响应。如果 TIdHTTP.Post() 引发 "Connection closed gracefully" 异常,这意味着服务器已关闭其端的套接字连接,而 TIdHTTP 仍在套接字上 sending/reading 数据。这可能会发生,例如,如果服务器不喜欢您的 POST 请求,或者如果它确定 JSON 在接收时无效。

此外,您的代码总体上可以简化。您滥用 TStringList 来处理 JSON 数据。

尝试更像这样的东西:

var
  sJSON, sRetorno: string;
  jsonLatestFirmware: TJSONValue;
  JsonToSend: TStringStream;
  ...
begin
  ...
  try
    sJSON := httpClient.Get(strGetLatest);
  except
    ShowMessage('There are no firmwares available for this product.');
    Exit;
  end;

  try
    jsonLatestFirmware := TJSONObject.ParseJSONValue(sJSON);
    try
      sJson := (jsonLatestFirmware as TJSONObject).get('data').JsonValue.ToString;
    finally
      jsonLatestFirmware.Free;
    end;

    JsonToSend := TStringStream.Create(sJson, TEncoding.UTF8);
    try
      JsonToSend.SaveToFile(ExtractFilePath(Application.ExeName) + 'finals\' + currentMACQR + '-' + currentAlive.IP + '.json');

      httpClient.Request.ContentType := 'application/json';
      sRetorno := httpClient.Post('http://' + currentAlive.IP + ':9335/upgrade?command=start', JsonToSend);
    finally
      JsonToSend.Free;
    end;
  except
    on E: Exception do
      ShowMessage('Error: '#13#10 + E.Message);
    Exit;
  end;

  lblInformacao.Visible := True;
  ShowMessage(sRetorno);
  ...
end;