2011-08-05 102 views
3

我在Delphi XE中使用DataSnap創建了一個REST Web服務。我使用XMLHttpRequest JavaScript對象調用服務器方法。我在XMLHttpRequest Open方法的第四個和第五個可選參數中傳遞用於驗證的用戶名和密碼。爲什麼OnUserAuthenticate在DataSnap REST服務器上被調用兩次?

當我在Delphi中加載我的DataSnap服務器並將調試器附加到IIS(我正在使用IIS 7.5)時,我可以看到第一次調用其中一個服務器方法導致DSAuthenticationManager的OnUserAuthenticate事件被調用兩次。

在第一次調用的OnUserAuthenticate事件處理程序的用戶和密碼參數爲空字符串。在第二次調用中,我在XMLHttpRequest Open方法中傳遞的用戶名和密碼出現在這些參數中。

用戶通過身份驗證後,使用XMLHttpRequest對任何服務器方法的任何後續調用都會導致對OnUserAuthenticate事件處理程序的單個調用,並分別在用戶和密碼參數中顯示用戶的用戶名和密碼。

我已經考察了許多了DataSnap類的源代碼,並沒有發現代碼,會導致這樣的效果,這是導致我認爲這種行爲可能會在瀏覽器或IIS起源。

爲什麼OnUserAuthenticate被在第一服務器的方法調用調用了兩次,什麼是生產這種效果呢?


針對墊德隆的回答,我顯示我在用的打電話給我的服務器方法的通用功能之一。這種特殊的方法進行同步呼叫。 baseURL參數通常是一個類似於http://mydomain.com/myservice/myservice.dll/datasnap/rest/tservermethods1的字符串,方法可能是myserver方法。附加參數用於將參數傳遞給服務器的方法:

function getJSONSync(baseURL, method) { 
    var request = new XMLHttpRequest(); 
    var url = baseURL + method; 
    for (var i= 2; i < arguments.length; i++) { 
    url += "/" + arguments[i]; 
    } 
    request.open("GET", encodeURI(url), false); 
    request.send(null); 
    if (request.status != 200) { 
    throw new Error(request.statusText); 
    } 
    return jQuery.parseJSON(request.responseText); 
} 

編輯:萊克斯李似乎是正確的,即IIS僅使用基本認證如果失敗的第一請求。另外,Mat是正確的,OnUserAuthenticate應該只在每個會話中調用一次。在檢查了他提供的鏈接後,很明顯我沒有捕獲並返回會話ID。這是一個可以工作的代碼示例,這次是非同步的。它捕獲sessionid並將其存儲在隱藏字段中。然後它將會話標識返回到Pragma標題中的每個後續調用中。

function getJSONAsync(callback, baseURL, method) { 
    var request = new XMLHttpRequest(); 
    var timedout = false; 
    request.onreadystatechange = function() { 
    if (request.readyState !== 4) { return } 
    if (timedout) { return } 
    if (request.status >= 200 && request.status < 300) 
    { 
     callback(jQuery.parseJSON(request.responseText)); 
     //store the sessionid in a hidden field 
     $("#sessionid").val(request.getResponseHeader('Pragma').split(',')[0].split("=")[1]); 
    } 
    } 
    var url = baseURL + method; 
    for (var i= 3; i < arguments.length; i++) { 
    url += "/" + arguments[i]; 
    } 
    request.open("GET", encodeURI(url), true); 
    //pass the sessionid in the Pragma header, if available 
    if (! $("#sessionid").val() == "") { 
    request.setRequestHeader("Pragma", "dssession="+$("#sessionid").val()); 
    } 
    //time out if request does not complete in 10 seconds 
    var timer = setTimeout(function() { timedout = true; request.abort(); }, 10000); 
    request.send(null); 
} 

編輯:當我先貼這兩個代碼示例我包括在開放的XMLHttpRequest方法的第四和第五參數樣本用戶名和密碼。我在測試過程中明確地傳遞了這些值。不推薦傳遞這樣的參數,所以我在這個編輯中刪除了這些參數。如果您省略了這些密碼,並且不使用其他方法(例如在您的第一個REST服務器方法調用的標頭中)傳遞它們,則瀏覽器將顯示一個對話框,詢問用戶這些信息。一旦提供了用戶名和密碼,瀏覽器將在會話的其餘部分記住這些信息。如果您關閉然後重新打開瀏覽器並調用另一個REST服務器方法,您將再次受到用戶名和密碼的挑戰。當然,這假設您通過OnUserAuthenticate事件處理程序拒絕無效用戶。

回答

3

那麼,這是其他技術(如ASP.NET)的典型IIS行爲。

初始請求不包含您的用戶憑據,並觸發401響應。然後第二個請求發送出您的用戶憑據,然後IIS可以返回200.

不確定這是否適用於DataSnap,但如果捕獲網絡數據包,則可能會發現它。

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate.aspx

+1

這看起來是正確的。我使用wireshark檢查了數據包。第一個請求不包含基本認證頭信息,而第二個請求包含。謝謝! –

2

我不能肯定地說你爲什麼看不到你的服務器代碼,以及你使用XHR的確切的JavaScript。OnUserAuthenticate旨在被召集一次,用於會話。

你可以看到XE的DataSnap REST協議這裏:http://docwiki.embarcadero.com/RADStudio/en/DataSnap_REST_Messaging_Protocol#Session_Information

意圖是第一次調用返回的會話ID,以及未來所有的呼叫都應該來指定。如果完成,所有呼叫,但第一次使用給定的會話ID跳過身份驗證過程,只有在分配給OnUserAuthorize事件時纔會發送。

如果您看到OnUserAuthenticate調用了兩次,那麼這可能是一個錯誤,除非它在XHR或IIS配置中有些奇怪。

如果你可以提供更多的細節,我會很樂意仔細看看。

+0

謝謝您的回覆。我將DataSnap服務器作爲純REST服務器調用,沒有任何代理生成的代碼。例如,我直接使用XMLHttpRequest(我沒有使用代理生成的serverMethods()函數)。以這種方式使用,每個服務器方法調用都會調用一次OnUserAuthenticate(除了在瀏覽器會話的第一次調用時調用兩次)。 我編輯了我的問題,並添加了XMLHttpRequest調用。 –

+0

Mat:我有這樣的印象,我應該從響應頭中讀取會話ID,並返回後續請求的頭文件。我目前沒有這樣做,並會研究它。 –

+0

墊子:再次感謝您的輸入和鏈接。我修改了我的問題以包含一個捕獲sessionID的異步調用,並在隨後調用DataSnap服務器時將其返回。當然,這段代碼並不檢查dssessionexpires的Pragma值,但是如果發佈了新的sessionid,它只會捕獲一個新的sessionid。現在,雖然有兩個對OnUserAuthenticate的調用(第一個沒有用戶名或密碼,第二個沒有),但沒有後續的服務器方法調用觸發OnUserAuthenticate。 –

相關問題