【问题标题】:Post Data to ASP .NET page using Delphi and TIdHttp使用 Delphi 和 TIdHttp 将数据发布到 ASP .NET 页面
【发布时间】:2010-11-23 23:37:11
【问题描述】:

我有一个像这样简单的 Asp .net 页面http://issamsoft.com/app2/page1.aspx 我想向它发布一些数据并从响应中提取数据, 通过使用 TIdHttp。我尝试在 Delphi2009 中这样做:

Procedure TForm1.Button1Click(Sender: TObject);
Const
VIEWSTATE = '/wEPDwUKMjA3NjE4MDczNmRkSxPt/LdmgqMd+hN+hkbiqIZuGUk=';
EVENTVALIDATION = '/wEWAwL40NXEDALs0bLrBgKM54rGBtmtdOYy+U7IFq8B25bYT1d4o1iK';
FORMPARAMS = 'TextBox1=Issam&Button1=Button';
URL = 'http://issamsoft.com/app2/page1.aspx';
var
http: TIdHttp;
lstParams: TStringList;
begin
 http := TIdHTTP.Create(self);
 lstParams := TStringList.Create;
 try
  lstParams.Add('__VIEWSTATE='+VIEWSTATE);
  lstParams.Add('__EVENTVALIDATION='+EVENTVALIDATION);
  lstParams.Add(FORMPARAMS);
  http.Request.ContentType := 'application/x-www-form-urlencoded';
  Memo1.Lines.Text := http.Post(url,lstParams);
 finally
  http.Free;
  lstParams.Free;
 end;

end;

但是 TIdhttp 总是报错(HTTP/1.1 500 Internal Server Error.) 我在 idHttp 单元中阅读了一些 cmets 讨论了 http 协议 v 1.1 的问题,如下所示:

目前在发布 POST 时,IdHTTP 会自动将协议设置为版本 1.0 独立于它最初的价值,这是因为 有些服务器不完全遵守 RFC。在 特别是,他们不尊重发送/不发送 Expect: 100-Continue 标题。在我们找到不违反 RFC 的最佳解决方案之前,我们 会将 POSTS 限制为 1.0 版。

我的代码有问题还是 TidHttp 错误?如果问题出在 TIdHttp,有什么解决方法吗?还是有其他使用 Indy 组件的解决方案?

此外。我使用 WebClient 在 C# 中制作了一个解决方案,效果非常好。

        private void button1_Click(object sender, EventArgs e)
    {
        WebClient myClient = new WebClient();
        string viewstate = HttpUtility.UrlEncodeUnicode(@"/wEPDwUKMjA3NjE4MDczNmRkSxPt/LdmgqMd+hN+hkbiqIZuGUk=");
        string eventvaildation = HttpUtility.UrlEncodeUnicode(@"/wEWAwL40NXEDALs0bLrBgKM54rGBtmtdOYy+U7IFq8B25bYT1d4o1iK");
        string postdata = "__VIEWSTATE=" + viewstate + "&" + 
            "__EVENTVALIDATION=" + eventvaildation + "&TextBox1=Issam&Button1=Button";
        myClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
        byte[] responce = myClient.UploadData("http://issamsoft.com/app2/page1.aspx", Encoding.ASCII.GetBytes(postdata));
        txtResponse.Text = Encoding.ASCII.GetString(responce);
    } 

在哪里可以找到(良好/受信任的)类,如 Delphi 中的 WebClient?免费首选:)

编辑: 我希望 VIEWSTATE,EVENTVALIDATION 的机制对你来说足够清楚,它们是服务器生成的哈希值,它们可能会改变(已经改变),我的原始项目有一段代码只是为了提取当前的 VIEWSTATE,EVENTVALIDATION 值,但我省略这部分只是为了使我的示例简单明了,因此当您想尝试上面的代码时,您必须从当前页面源中获取 VIEWSTATE,EVENTVALIDATION 值。

【问题讨论】:

  • 我尝试使用 TIdURI 对 VIEWSTATE 参数进行编码,并尝试将(预)编码的参数传递给 TIdhttp。而且它也没有用:(
  • 我希望 VIEWSTATE,EVENTVALIDATION 的机制对你来说足够清楚,它们是服务器生成的哈希值,它们可能会改变(已经改变),我的原始项目有一段代码只是为了提取当前的 VIEWSTATE,EVENTVALIDATION 值,但我省略了这部分只是为了让我的示例简单明了,所以当你想尝试上面的代码时,你必须从当前页面源中获取 VIEWSTATE,EVENTVALIDATION 值。
  • 您的问题现在得到充分回答了吗?

标签: asp.net delphi delphi-2009 indy


【解决方案1】:

这很可能是因为在 C# 中,您使用与号 (&) 字符来连接 VIEWSTATE、EVENTVALIDATION 和 FORPARAMS。

但在 Delphi 中,您使用 TStringList 和 Add。 Add 不会在加法之间放置 & 符号 (&),而是 CR+LF。

因此,您在 Delphi 中发送的数据与在 C# 中不同。

如果您更改 Delphi 中的字符串连接逻辑以匹配 C# 中的逻辑,它应该可以工作,无论您在 Delphi 端使用哪种 WebClient。

--杰罗恩

编辑:20090830

此代码有效;使用 Fiddler2 检查 C# 和 IE7 输出,我发现您还需要转义 '/' 和 '=' 字符。

Edit2:20090831

不知何故,网站上的 VIEWSTATE 和 EVENTVALIDATION 自昨天以来发生了变化(尽管我不确定为什么)。我已经更改了代码,它在发布我对此答案的更改时有效。 这是从 Internet Explorer 7 中发布页面时每个 Fiddler 捕获的新请求内容:

__VIEWSTATE=%2FwEPDwUKMjA3NjE4MDczNmRk5%2FC2iWwvlAB3L1wYzRpm3KZhRC0%3D&__EVENTVALIDATION=%2FwEWAwLXzuATAuzRsusGAoznisYGSYOqDGy4vMunY6A8xi6ahQEPI5Q%3D&TextBox1=Issam&Button1=Button

这是从 Delphi 示例发布时的新请求:

__VIEWSTATE=%2FwEPDwUKMjA3NjE4MDczNmRk5%2FC2iWwvlAB3L1wYzRpm3KZhRC0%3D&__EVENTVALIDATION=%2FwEWAwLXzuATAuzRsusGAoznisYGSYOqDGy4vMunY6A8xi6ahQEPI5Q%3D&TextBox1=Issam&Button1=Button

下面的示例现在给出了这个结果(注意结果中的“Issam”):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>

</title></head>
<body>
    <form name="form1" method="post" action="page1.aspx" id="form1">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA3NjE4MDczNg9kFgICAw9kFgICBQ8PFgIeBFRleHQFDVdlbGNvbWUgSXNzYW1kZGSCDMOkTMjkZJgqLkhpK99twpD5+A==" />

<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwKr+O/BBQLs0bLrBgKM54rGBlueO5BU/6BAJMZfHNwh5fsQFuAm" />
    <div>

        <input name="TextBox1" type="text" value="Issam" id="TextBox1" />
        <input type="submit" name="Button1" value="Button" id="Button1" />

        <br />

        <span id="Label1">Welcome Issam</span>
        <br />

    </div>
    </form>
</body>
</html>

Edit3:20090831

确实,VIEWSTATE 和 EVENTVALIDATION 再次发生了变化:不知何故,它们一直在变化:似乎涉及到时间因素。

所以我修改了代码,现在它可以从 GET 请求中提取 VIEWSTATE 和 EVENTVALIDATION,然后将其放入 POST 请求中。

此外,'+' 和 ':' 似乎也需要转义。 所以我深入研究了 ASP.NET 页面生成逻辑,发现他们使用HttpUtility.UrlEncode 进行转义,最终检查 HttpUtility.IsSafe 哪些字符要转义。 所以我改编了将Reflector 逆向工程的 Delphi 代码放入 THttpUtility 类中。

这是最终代码:

{$DEFINE FIDDLER}

unit HttpPostExampleFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;

type
  THttpPostExampleForm = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    procedure Log(const What: string; const Content: string);
    procedure LogClear;
  end;

var
  HttpPostExampleForm: THttpPostExampleForm;

implementation

uses
  HttpUtilityUnit;

{$R *.dfm}

procedure AddFormParameter(const StringStream: TStringStream; const ParameterName: string; const ParameterValue: string);
var
  EncodedParameterValue: string;
begin
  StringStream.WriteString(ParameterName);
  StringStream.WriteString('=');
  EncodedParameterValue := THttpUtility.UrlEncode(ParameterValue);
  StringStream.WriteString(EncodedParameterValue);
end;

procedure AddFormParameterSeparator(const StringStream: TStringStream);
begin
  StringStream.WriteString('&');
end;

function ExtractHiddenParameter(const ParameterName: string; const Request: string): string;
//<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA3NjE4MDczNg9kFgICAw9kFgICBQ8PFgIeBFRleHQFDVdlbGNvbWUgSXNzYW1kZGSCDMOkTMjkZJgqLkhpK99twpD5+A==" />
const
  PrefixMask = 'input type="hidden" name="%s" id="%s" value="';
  Suffix = '" />';
var
  Prefix: string;
  PrefixLength: Integer;
  PrefixPosition: Integer;
  SuffixPosition: Integer;
begin
  Prefix := Format(PrefixMask, [ParameterName, ParameterName]);
  PrefixPosition := Pos(Prefix, Request);
  if PrefixPosition = 0 then
    Result := ''
  else
  begin
    PrefixLength := Length(Prefix);
    Result := Copy(Request,
      PrefixPosition + PrefixLength,
      1 + Length(Request) - PrefixPosition - PrefixLength);
    SuffixPosition := Pos(Suffix, Result);
    if SuffixPosition = 0 then
      Result := ''
    else
      Delete(Result, SuffixPosition, 1 + Length(Result) - SuffixPosition);
  end;
end;

procedure THttpPostExampleForm.Button1Click(Sender: TObject);
const
  DefaultVIEWSTATE = '/wEPDwUKMjA3NjE4MDczNmRk5%2FC2iWwvlAB3L1wYzRpm3KZhRC0=';
  DefaultEVENTVALIDATION = '/wEWAwLXzuATAuzRsusGAoznisYGSYOqDGy4vMunY6A8xi6ahQEPI5Q=';
  FORMPARAMS = 'TextBox1=Issam&Button1=Button';
  URL = 'http://issamsoft.com/app2/page1.aspx';
  __VIEWSATE = '__VIEWSTATE';
  __EVENTVALIDATION = '__EVENTVALIDATION';
var
  VIEWSTATE: string;
  EVENTVALIDATION: string;
  getHttp: TIdHttp;
  getRequest: string;
  postHttp: TIdHttp;
  ParametersStringStream: TStringStream;
  postRequest: string;
begin
  LogClear();
  getHttp := TIdHTTP.Create(self);
  try
    getRequest := getHttp.Get(URL);
    Log('GET Request', getRequest);
    VIEWSTATE := ExtractHiddenParameter(__VIEWSATE, getRequest);
    EVENTVALIDATION := ExtractHiddenParameter(__EVENTVALIDATION, getRequest);
    Log('Extracted VIEWSTATE', VIEWSTATE);
    Log('Extracted EVENTVALIDATION', EVENTVALIDATION);
  finally
    getHttp.Free();
  end;

  postHttp := TIdHTTP.Create(self);
{$IFDEF FIDDLER}
  postHttp.ProxyParams.ProxyServer := '127.0.0.1';
  postHttp.ProxyParams.ProxyPort := 8888;
{$ENDIF FIDDLER}
  postHttp.HTTPOptions := postHttp.HTTPOptions + [hoKeepOrigProtocol];
  ParametersStringStream := TStringStream.Create('');
  try
    AddFormParameter(ParametersStringStream, __VIEWSATE, VIEWSTATE);
    AddFormParameterSeparator(ParametersStringStream);
    AddFormParameter(ParametersStringStream, __EVENTVALIDATION, EVENTVALIDATION);
    AddFormParameterSeparator(ParametersStringStream);
    ParametersStringStream.WriteString(FORMPARAMS);
    postHttp.Request.ContentType := 'application/x-www-form-urlencoded';
    ParametersStringStream.Seek(0, soFromBeginning);
    Log('POST Parameters', ParametersStringStream.DataString);
    postRequest := postHttp.Post(url, ParametersStringStream);
    Log('POST Request', postRequest);
  finally
    postHttp.Free;
    ParametersStringStream.Free;
  end;
end;

procedure THttpPostExampleForm.Log(const What, Content: string);
begin
  Memo1.Lines.Add(What + '=');
  Memo1.Lines.Add(Content);
end;

procedure THttpPostExampleForm.LogClear;
begin
  Memo1.Lines.Clear;
end;

end.

还有 THttpUtlity 类:

unit HttpUtilityUnit;

interface

type
  THttpUtility = class
  private
    class function IsSafe(const ch: Char): boolean;
  public
    class function UrlEncode(s: string): string;
  end;

implementation

uses
  Classes, SysUtils;

class function THttpUtility.IsSafe(const ch: Char): boolean;
begin
  if ((((ch >= 'a') and (ch <= 'z')) or ((ch >= 'A') and (ch <= 'Z'))) or ((ch >= '0') and (ch <= '9'))) then
    Result := True
  else
    case ch of
      '''',
        '(',
        ')',
        '*',
        '-',
        '.',
        '_',
        '!':
        Result := True;
    else
      Result := False;
    end;
end;

class function THttpUtility.UrlEncode(s: string): string;
var
  ch: Char;
  HexCh: string;
  StringStream: TStringStream;
  Index: Integer;
  Ordinal: Integer;
begin
  StringStream := TStringStream.Create('');
  try
    //Note: this is not yet UTF-16 compatible; check before porting to Delphi 2009
    for Index := 1 to Length(s) do
    begin
      ch := s[Index];
      if IsSafe(ch) then
        StringStream.WriteString(Ch)
      else
      begin
        Ordinal := Ord(Ch);
        HexCh := IntToHex(Ordinal, 2);
        StringStream.WriteString('%'+HexCh);
      end;
    end;

    Result := StringStream.DataString;
  finally
    StringStream.Free;
  end;
end;

end.

这应该能让你继续前进。

--杰罗恩

【讨论】:

  • +1.. 这应该让 Issam Ali 继续前进。我会为尝试和提供代码示例加分,但我不允许这样做。
  • 感谢 Jeroen,我真的很感谢您的帮助,但这不起作用,如果您检查响应,您会看到 Label1 的值仍然是“Lable1”,而它应该是“Welcome伊萨姆”。为什么它没有给你错误 500 因为你没有发布正确的参数,参数应该像这样 param1=v1&param2=v2... 但实际上你发布了这个数据:'__VIEWSTATE%3D%2FwEPDwUKMjA3NjE4MDczNmRkSxPt%2FLdmgqMd+hN +hkbiqIZuGUk=&__EVENTVALIDATION%3D%2FwEWAwL40NXEDALs0bLrBgKM54rGBtmtdOYy+U7IFq8B25bYT1d4o1iK&TextBox1=Issam&Button1=Button' 所以服务器看不到参数__VIEWSTATE
  • BTW:TIDHttp 的 Post 方法将换行符转换为 '&'
  • 正如我在第一条评论中所说,我尝试发布编码参数(使用 C# 应用程序生成它),并告诉 TIdhttp 不要再次编码它(hoForceEncodeParams = false)!不工作:)
  • ((parameters)) 不是 ((parameters)) 抱歉 :)
【解决方案2】:

您需要更新 VIEWSTATE 和 EVENTVALIDATION 值。

运行该代码会导致 500 错误,但如果我打开页面并使用网页源中的新 __VIEWSTATE 和 __EVENTVALIDATION 值,则不会再导致 500 错误。

我不知道为什么 C# 客户端会在 Delphi 没有的情况下工作,但也许它不再工作了?

这看起来像是一个 ASP 问题,而不是 Delphi。 Indy (TIdHTTP) 和 Synapse 都是 Delphi 的优秀套接字库。

【讨论】:

  • -1 实际上,您应该将 VIEWSTATE 和 EVENTVALIDATION 视为只读。它们是属性包,由 ASP.NET 在内部用于在服务器处理 POST 请求(带有新的表单数据)时重建“之前”状态。
  • 对,只需更新Delphi代码中的值,然后发布的代码就可以正常工作了。我并不是要更改 ASP.NET 页面。
【解决方案3】:

如果您在 TIdHTTP 实例 HTTPOptions 属性中包含 hoKeepOrigProtocol 选项,它将不会回退到 HTTP 1.0。

这记录在您引用的评论文本下方的几行:)

    // If hoKeepOrigProtocol is SET, is possible to assume that the developer
    // is sure in operations of the server
    if not (hoKeepOrigProtocol in FOptions) then begin
      FProtocolVersion := pv1_0;
    end;

【讨论】:

  • 您是否尝试过像Don's Proxy(来自Sourceforge,donsproxy.sourceforge.net)这样的HTTP 代理来记录Delphi 和C# 客户端的HTTP 请求?这个工具对于追踪请求之间的差异非常有用。
  • 因此您可以将请求保存到文件中,并使用差异工具查找 Delphi 和 C# 版本之间的差异(在 HTTP 标头或负载中)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-12
  • 2011-07-11
  • 1970-01-01
  • 1970-01-01
  • 2014-06-08
  • 2023-03-27
相关资源
最近更新 更多