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;