Отправка данных на страницу ASP .NET с использованием Delphi и TIdHttp - PullRequest
4 голосов
/ 29 августа 2009

У меня есть страница 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, рассказывает о проблемах с протоколом http v 1.1, например:

В настоящее время при выдаче POST IdHTTP автоматически устанавливает версию протокола 1.0 независимо от того, какое значение оно имело изначально, потому что Есть некоторые серверы, которые не соблюдают RFC в полной мере. В в частности, они не уважают отправку / не отправку Expect: 100-Continue заголовок. Пока мы не найдем оптимальное решение, которое НЕ нарушает RFC, мы ограничит ПОСТЫ до версии 1.0.

что-то не так с моим кодом или это ошибка TidHttp? и если проблема в TIdHttp, есть ли обходной путь? или есть другое решение, использующее компоненты Indy?

кроме того. Я сделал решение в C # с использованием WebClient, и оно работает очень хорошо.

        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);
    } 

где я могу найти (хороший / надежный) класс, такой как WebClient в Delphi? бесплатно предпочтительнее:)

Edit: Я надеюсь, что механизм VIEWSTATE, EVENTVALIDATION достаточно понятен для вас, они являются хеш-значениями, сгенерированными сервером, и они могут измениться (уже изменились), в моем оригинальном проекте есть фрагмент кода, предназначенный только для извлечения текущих значений VIEWSTATE, EVENTVALIDATION, но я опустите эту часть, чтобы сделать мой пример простым и понятным, поэтому, если вы хотите попробовать приведенный выше код, вы должны взять значения VIEWSTATE, EVENTVALIDATION из текущего источника страницы.

Ответы [ 3 ]

4 голосов
/ 30 августа 2009

Скорее всего, это связано с тем, что в C # вы используете символы амперсанда ( & ) для объединения VIEWSTATE, EVENTVALIDATION и FORMPARAMS.

Но в Delphi вы используете TStringList и Add. При добавлении добавляется не амперсанд ( & ), а CR + LF между дополнениями.

Следовательно, вы отправляете в Delphi другие данные, чем в C #.

Если вы измените логику конкатенации строк в Delphi, чтобы она соответствовала логике в C #, она должна работать, независимо от того, какой тип WebClient вы используете на стороне Delphi.

- Йерун

Редактировать: 20090830

Этот код работает; Изучив выходные данные C # и IE7 с помощью Fiddler2, я обнаружил, что вам также необходимо экранировать символы '/' и '='.

Редактировать2: 20090831

Каким-то образом VIEWSTATE и EVENTVALIDATION на сайте изменились со вчерашнего дня (хотя я не уверен, почему). Я изменил код, и он работает во время публикации моих изменений в этом ответе. Это новый контент запроса, полученный с помощью Fiddler при публикации страницы из Internet Explorer 7:

__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>

Редактировать3: 20090831

И действительно, ПРОСМОТР и СОБЫТИЕ снова изменились: почему-то они продолжают изменяться: кажется, что есть фактор времени.

Итак, я адаптировал код, теперь он может извлекать VIEWSTATE и EVENTVALIDATION из запроса GET, а затем помещать его в запрос POST.

Кроме того, оказалось, что '+' и ':' также необходимо экранировать. Поэтому я покопался в логике генерации страниц ASP.NET и обнаружил, что они используют HttpUtility.UrlEncode для выполнения экранирования, который в конечном итоге проверяет HttpUtility.IsSafe , какие символы экранировать. Поэтому я адаптировал преобразованный код Delphi Reflector в класс 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.

Это должно помочь вам.

- Йерун

0 голосов
/ 29 августа 2009

Вам необходимо обновить значения VIEWSTATE и EVENTVALIDATION.

Запуск этого кода вызывает ошибку 500, но если я открою страницу и использую новые значения __VIEWSTATE и __EVENTVALIDATION из источника веб-страницы, это больше не вызывает ошибку 500.

Я не знаю, почему клиент C # работал бы, когда не работал Delphi, но, может быть, он больше не работает?

Это похоже на проблему ASP, а не Delphi. Indy (TIdHTTP) и Synapse являются хорошими библиотеками сокетов для Delphi.

0 голосов
/ 29 августа 2009

Если вы включите параметр hoKeepOrigProtocol в свойство HTTPOptions экземпляра TIdHTTP, он не вернется к 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;
...