在 Delphi 中使用 idHTTP 发布表单数据

Posting Form Data With idHTTP in Delphi

我正在使用 Delphi 和 IdHTTP 库从这个 URL (http://apply.dataprocessors.com.au) 中获取一个表单,处理这个谜题并 post 给出正确答案。但是,显然我没有正确处理方法 POST,一旦检索到的响应是:"Problem with submission. Please try again."

下面是Delphi源代码:

procedure TForm1.Button1Click(Sender: TObject);
var
  sGET, sPOST, sGET_count: String;
  countCircles: integer;
  parameters: TStringList;
begin
  parameters := TStringList.Create;
  sGET := http.Get('http://apply.dataprocessors.com.au');
  sGET_count := StringReplace(sGET, '$', ' ', [rfReplaceAll, rfIgnoreCase]);
  sGET_count := StringReplace(sGET_count, 'unfilled', '$', [rfReplaceAll, rfIgnoreCase]);
  countCircles := 120 - sGET_count.CountChar('$');

  parameters.Add('title=submit');
  parameters.Add('value=' + countCircles.ToString());
  parameters.Add('submit=Submit');
  http.Request.ContentType := 'application/x-www-form-urlencoded';
  sPOST := http.Post('http://apply.dataprocessors.com.au/?', parameters);
  txtPost.Lines.Add(sPOST);
end; 

Form retrieved

HTML代码:

<html>
  <head>
  </head>
<body>
<form action="" method="POST">
      <input type="hidden" name="title" value="submit">
      <p>How many filled circles do you see:? <br><img src="unfilled_O.gif" height="12" width="12"><br></p>
      <p>Answer: <input type="text" name="value" value="" size="10"></p>
      <p><input type="Submit" value="Submit">
</form></body>
</html>

从 POST 方法检索到的响应:

<html>
  <head>
  </head>
  <body>
  <p>Problem with submission. Please try again.</p></body>
</html>

使用 F12(或 HTTP 代理)捕获您的浏览器通信并查看 "working" 请求的样子。

工作请求是否使用cookies?如果是,请为 IdHttp 启用 cookie 支持。

我认为您的 TIdHTTP 代码没有问题(尽管 Post() 的 URL 末尾的 /? 不是必需的). 您的 TIdHTTP 代码中缺少步骤。详情见下文。

我还发现您的 StringReplace() 代码有问题:

sGET := http.Get('http://apply.dataprocessors.com.au');
sGET_count := StringReplace(sGET, '$', ' ', [rfReplaceAll, rfIgnoreCase]);
sGET_count := StringReplace(sGET, 'unfilled', '$', [rfReplaceAll, rfIgnoreCase]);

注意第二个StringReplace()的第一个参数。在第二个调用中,您正在搜索 TIdHTTP.Get() 返回的 原始 HTML。您不是在搜索第一个 StringReplace() 的结果。然后当您 re-assign sGET_count 时,您将完全丢弃第一个 StringReplace() 的结果,有效地使第一个调用成为 no-op。您可能打算改为这样做:

sGET := http.Get('http://apply.dataprocessors.com.au');
sGET_count := StringReplace(sGET, '$', ' ', [rfReplaceAll, rfIgnoreCase]);
sGET_count := StringReplace({sGET}sGET_count, 'unfilled', '$', [rfReplaceAll, rfIgnoreCase]);

更新:当我在我的网络浏览器中访问 URL 时,我发现您的代码没有考虑到一些事情。第一,网络表单包含一个 jobref 字段,您未将其包含在 POST 提交中。但更重要的是,当网页被真实的网络浏览器检索时,相关网页会调用 JavaScript 函数,但当您当前的 TIdHTTP 设置检索时则不会。 Javascript 位于 http://apply.dataprocessors.com.au/funcs1.js 并包含一个动态添加 cookie 到网络浏览器的命令:

function init() {
  document.cookie = 'pc3o=d2r22; path=';
}

此命令由 HTML:

<html>
  <head>
    ...
    <script type="text/javascript" src="funcs1.js"></script>
  </head>
<body onLoad="init()">
  ...
  </html>

当网络浏览器提交网络表单时,除了在 HTML被检索。您需要将 TIdHTTP.AllowCookies 属性 设置为 True(默认情况下为 False),以便 TIdHTTP.Post() 可以发回 cookie,但是 Post() 只会发送 PHP cookie,而不是 Javascript cookie,原因很明显(TIdHTTP 不调用 client-side 脚本)。

如果您更改 TIdHTTP.Request.UserAgent 属性 以模仿真实的网络浏览器,而不是使用 Indy 的默认设置 UserAgent,下载的 HTML 将包含 JavaScript 参考。这是可选的,因为您可以手动添加 Javascript cookie(幸运的是它是静态的,因此您不需要实际解析 Javascript,但如果您愿意,可以)到 TIdHTTP.CookieManager 不管 UserAgentPost() 仍会发送它,服务器似乎并不关心 HTML 是否包含 JavaScript 引用。

话虽如此,以下代码对我有用(我让服务器向 POST 发送成功响应):

procedure TForm1.Button1Click(Sender: TObject);
var
  sGET, sPOST, sGET_count: String;
  countCircles: integer;
  parameters: TStringList;
begin
  parameters := TStringList.Create;
  try
    http.AllowCookies := True;
    // optional: http.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';

    sGET := http.Get('http://apply.dataprocessors.com.au');
    http.CookieManager.AddServerCookie('pc3o=d2r22; path=', http.URL);

    sGET_count := StringReplace(sGET, '$', '', [rfReplaceAll, rfIgnoreCase]);
    sGET_count := StringReplace(sGET_count, '"filled_O.gif"', '$', [rfReplaceAll, rfIgnoreCase]);
    countCircles := sGET_count.CountChar('$');

    parameters.Add('title=submit');
    parameters.Add('jobref=');
    parameters.Add('value=' + countCircles.ToString());

    http.Request.ContentType := 'application/x-www-form-urlencoded';
    http.Request.Referer := 'http://apply.dataprocessors.com.au';

    sPOST := http.Post('http://apply.dataprocessors.com.au', parameters);
    ShowMessage(sPOST);
  finally
    parameters.Free;
  end;
end;