【问题标题】:How to send content with TIdHttp GET request?如何使用 TIdHttp GET 请求发送内容?
【发布时间】:2019-01-23 15:07:13
【问题描述】:

我正在尝试访问具有使用 GET 动词的端点的 REST 服务器,同时它还需要在正文中发送 json 数据

我正在尝试使用 Get 方法通过 TIdHttp 类来实现。

现在我用 json 数据创建一个 TStringStream,然后将该流分配给 TIdHttp 对象的 Request.Source 属性。

但是,服务器以错误代码响应,表明它没有收到正文。

如何使用 TIdHttp 发送带有 GET 请求的正文?

【问题讨论】:

  • 设计该服务器的人犯了一个巨大的错误。虽然 HTTP 标准在技术上确实允许 GET 中的正文,但它不受官方支持。似乎TIdHTTP 只是忽略了GET 请求中的正文。阅读更多:stackoverflow.com/questions/978061/http-get-with-request-body
  • 长话短说,您的选择是 A) 破解 TIdHTTP 以使其发送正文,或 B) 找到另一个支持此功能的 HTTP 组件(这不太可能)。
  • @JerryDodge “设计该服务器的人更关注文档化标准,而不是广泛实施的事实标准”。在那里,我为你修好了。当我在一家构建集成中间件的公司工作时,我们遇到了完全相同的问题——你会惊讶于有多少公司实施了 REST Api,在 Api 端点行为方面 [gasp] 遵循 HTTP 标准,而不是遵循 浏览器行为人群。我们只是简单地修改了我们的 Indy 代码以使其成为可能。 :耸肩::)

标签: http delphi indy idhttp


【解决方案1】:

正如 Remy 建议的那样,您可以破解一个“访问器”类,以设法通过类型强制调用来访问此方法。

或者,您可以简单地继承 TIdHttp 并添加一个适当的新 Get() 便利方法,遵循为所有其他此类建立的模式方法。

例如类似:

interface

  type
    TOurHttp = class(TIdHttp)
    public
      procedure Get(aUrl: String; aRequestBody, aResponseContent: TStream);
    end;

implementation

  procedure TOurHttp.Get(aUrl: String; aRequestBody, aResponseContent: TStream);
  begin
    DoRequest(Id_HttpMethodGet, aUrl, aRequestBody, aResponseContent, []);
  end;

这是我们在我之前工作过的一家公司所做的,构建集成/中间件软件,因为在很多示例中,提供的 Indy 便捷方法(当时)并未涵盖我们所有的用例,而不仅仅是 GET ,尤其是涉及到 REST Api 时。请注意,上面不是我们当时的实际代码,我不再可以访问它。

我们没有直接使用TIdHttp 类,而是使用了TOurHttp(不是真名),它赋予了许多对我们有用的额外便利方法。我们还发现我们必须对其他方面进行更改和扩展,例如 IgnoreReplies 行为,以适应各种“流氓”REST Api 服务器行为(并非所有这些行为都符合 HTTP 标准),这又是您在大多数用例。

当然,您可以尝试要求 Api 或 HTTP 服务器的提供者更改他们的端,而不是修复您的客户端行为。祝你好运。 ;)

背景阅读

HTTP 协议规范确实允许 GET 请求提交请求正文,但绝大多数实现不允许(或干脆忽略任何此类内容)。这可能是因为从历史上看,绝大多数实现都以浏览器行为为目标或受其驱动,再加上 HTTP 规范不幸在该领域允许一些“回旋余地”这一事实。

这导致了我们今天所处的位置,在撰写本文时(2019 年 1 月),HTTP 规范的相关部分 RFC7231 has this to say about content in a GET request

GET 请求消息中的有效负载没有定义的语义; 在 GET 请求上发送有效负载正文可能会导致一些现有的 拒绝请求的实现。

即请求内容允许的,因为它没有被明确禁止,并且它的使用被确认为可能并且有时是预期的(有时只是被拒绝)。但是您不能依赖服务器对此类请求采取任何特定解释(甚至接受)。

这当然无助于处理决定其特定解释的特定服务器的客户端。通过以这种方式承认不好的做法,HTTP 规范的这种迭代就没有多大用处了。它只是描述了实践中的差异,而不是提供可以测试实现的合规性/准确性的规范。

【讨论】:

  • 很好的建议是创建一个派生类并添加一个带有额外 aRequestBody 参数的 Get 方法
【解决方案2】:

分配TIdHTTP.Request.Source 属性将不起作用,因为当TIdHTTP.Get() 调用TIdHTTP.DoRequest() 内部传递nil 为其ASource 参数时,Source 将被nil 替换。

因此,要按照您的要求进行操作,您必须直接致电 DoRequest()。但是,它是一个protected 方法,因此您必须使用访问器类来访问它。

例如:

type
  TIdHTTPAccess = class(TIdHTTP)
  end;

var
  QueryData, ReplyData: TStream;
begin
  QueryData := TStringStream.Create('... json data here...', TEncoding.UTF8);
  try
    IdHTTP1.Request.ContentType := 'application/json';
    IdHTTP1.Request.CharSet := 'utf-8';

    //Result := IdHTTP1.Get('http://...');
    ReplyData := TMemoryStream.Create;
    try
      TIdHTTPAccess(IdHTTP1).DoRequest(Id_HTTPMethodGet, 'http://...', QueryData, ReplyData, []);
      ReplyData.Position := 0;
      Result := ReadStringAsCharset(ReplyData, IdHTTP1.Response.Charset);
    finally
      ReplyData.Free;
    end;
  finally
    QueryData.Free;
  end;
end;

【讨论】:

  • 感谢 Remy 的详细解释。我想知道 TIdHttp 是否不支持开箱即用,或者我做错了什么。事实证明它不支持开箱即用,我认为原因很明显。您的建议以及@Deltics 的建议对我很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-25
  • 2018-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多