1. 程式人生 > >C/S應用程式中進行HTTP登入,獲取相應的資料。

C/S應用程式中進行HTTP登入,獲取相應的資料。

       前段時間處理三星的一個裝置,用其的庫時,登陸時需要指定裝置型別,應用程式裡相關的資料裡也沒有儲存裝置的型別欄位,應用裡在我不知道這個裝置型號時,問廠家怎麼解決,回覆我說一個一個的裝置型別去連線吧! 很變態呀,幾個型別的裝置試下來時間很長。由於他的裝置支援HTTP,RTSP協議,在HTTP管理時,有一個頁面是可以看到其裝置型別的,一想,每一種裝置都有WEB服務,都是同一樣的介面,這樣先登入時,我在應用程式裡做一個WEB請求來獲取其裝置型號先,再用TCP去連線,而這個WEB服務是基本一直開啟的。

       看了一個其HTTP協議的說明,WEB的裝置登入說是用RFC2069協議的"Standard http digest access authentication method of RFC2069"認證,瞭解的一下RFC2069,再用WireShark跟蹤了登入過程, 其實也不是很Standard呀,很多細節不同的。

       Delphi裡有一個TIdDigestAuthentication類,就是用來實現這個的,不過這個是標準的,還是和裝置的有一定的差異,HTTP請求反證就是一個有特定格式的字串,所以我做一個類過載了它的Authentication方法:

  TDigestAuth = class(TIdDigestAuthentication)
 private
  FWWWAuthenticate: string;
    FBASEURI: string;
 public
  constructor Create(WWWAuthenticate,buri: string);
  function Authentication: String;override;
  property WWWAuthenticate: string read FWWWAuthenticate write FWWWAuthenticate;
  property BASEURI: string read FBASEURI write FBASEURI;
 end;

自己再在這個方法裡生成特定的請求就好了:

function TDigestAuth.Authentication: String;
var
  authtext,AuthorizationStr: string;
  A1,A2,Method: string;
  realm,nonce,uri,qop,cnonce,nc,response: string;
  ipos,Steps: integer;
begin
  authtext := WWWAuthenticate;
  ipos := Pos('nonce="',authtext);
  if ipos > 0 then
  begin
    Delete(authtext,1,ipos+6);
  end;
  ipos := pos('"',authtext);
  if ipos > 0 then
  begin
    nonce := Copy(authtext,1,ipos-1);
  end;
 
  realm := 'iPolis';
  qop := 'auth';
  uri := BASEURI;


  cnonce := '01a34a';
  Steps := 1;
  nc := Format('%.8d',[Steps]);
  A1 := MD5Print(MD5String(UserName + ':' + realm + ':' + Password));
  Method := 'GET';
  A2 := MD5Print(MD5String(Method + ':' + uri));
  response :=MD5Print(MD5String(A1+':'+nonce+':'+ nc + ':'+ cnonce + ':' + qop +':'+A2));


  AuthorizationStr := Format('Digest username="%s",realm="%s",nonce="%s",uri="%s",'+
    'cnonce="%s",nc=%s,response="%s",qop="%s"',
    [UserName,realm,nonce,uri,cnonce,nc,response,qop]);
  Result := AuthorizationStr;
end;

或下載一個RFC2069的文件看看。

上面這個方法返回的就是最終的請求登入時的HTTP內容了。

在應用的裡面我再寫一個如下的過程來登入和請求裝置的型別:

function TXXXSAM.GetDeviceModelName: string;
  function FromHtmlResponseGetModelName(ResponseText: string): string;
  var
    ts: TStringList;
    i,iPos: integer;
    modelStr: string;
  begin
    modelStr := '';
    Result := '';
    ResponseText := StringReplace(ResponseText,#13#10,'#',[rfReplaceAll]);
    iPos := Pos('model',ResponseText);
    modelStr := Copy(ResponseText,iPos,Length(ResponseText));
    iPos := Pos('#',modelStr);
    Result := Copy(modelStr,7,iPos-7);
  end;
var
  URL,uri:string;
  IdHTTP: TIdHTTP;
  ResponseStream: TStringStream;
  DigestAuth: TDigestAuth;
begin
  Result := '';
  URL := Format('http://%s%s',[IP,SAMHTTPGETPARAM]);
  uri := SAMHTTPGETPARAM;
  IdHTTP := TIdHTTP.Create(nil);
  ResponseStream := TStringStream.Create('');
   try


    try
      ResponseStream.Seek(0,0);
      IdHTTP.Get(URL,ResponseStream);
    except
      ;
    end;
    if (IdHttp.ResponseCode = 401) and  //401 請求登入
      (pos('auth',IdHttp.Response.WWWAuthenticate.Text) > 0) then
    begin
        DigestAuth := TDigestAuth.Create(IdHttp.Response.WWWAuthenticate.Text,uri);
        IdHTTP.Request.Authentication := DigestAuth;
        IdHTTP.Request.Authentication.Username := self.UserName;
        IdHTTP.Request.Authentication.Password := self.PassWord;
        IdHTTP.Request.SetHeaders;
        try
          ResponseStream.Seek(0,0);
          IdHttp.Get(URL,ResponseStream);
        except
          ;
        end;


        if IdHttp.ResponseCode = 404 then //請求的頁面不存在:三星的HTTP協議獲取引數有多種型別,有的型別的地址不一樣
        begin
          URL := Format('http://%s%s',[IP,SAMHTTPGETPARAM2]);
          uri := SAMHTTPGETPARAM2;
          DigestAuth.BASEURI := uri;
          IdHTTP.Request.SetHeaders;
          try
            ResponseStream.Seek(0,0);
            IdHttp.Get(URL,ResponseStream);
          except
            ;
          end;
        end;


        if IdHttp.ResponseCode = 200 then   //成功
          Result := FromHtmlResponseGetModelName(ResponseStream.DataString)
        else if IdHTTP.ResponseCode = 401 then
          LastMessageInfo := 'HTTP請求登入失敗'
        else
        begin
          LastMessageInfo := 'HTTP請求資訊失敗:'+ IdHttp.ResponseText;
          writelog(SAMGetLastError(LastMessageInfo));
        end; 
    end
    else if IdHttp.ResponseCode = 200 then   //成功
    begin
        Result := FromHtmlResponseGetModelName(ResponseStream.DataString);
    end
    else
    begin
      LastMessageInfo := 'HTTP請求資訊失敗:'+ IdHttp.ResponseText;
      writelog(SAMGetLastError(LastMessageInfo));
    end;
   finally
    ResponseStream.Free;
    IdHTTP.Free;
   end;
end;