Why is OnUserAuthenticate being called twice on a DataSnap REST server?
I have created a REST Web service using DataSnap in Delphi XE. I am calling server methods using the XMLHttpRequest JavaScript object. I am passing the username and password for authentication in the fourth and fifth optional parameters of the XMLHttpRequest Open method.
When I load my DataSnap server in Delphi and attach the debugger to IIS (I am using IIS 7.5) I can see that the first time I call one of the server methods results in the DSAuthenticationManager's OnUserAuthenticate event being called twice.
In the first call the user and password parameters of the OnUserAuthenticate event handler are empty strings. On the second call, the username and password that I passed in the XMLHttpRequest Open method appear in those parameters.
Once the user has been authenticated, any subsequent calls to any of the server methods using XMLHttpRequest results in a single call to the OnUserAuthenticate event handler, with the user's username and password appearing in the user and password parameters, respectively.
I have inspected the source code for a number of the DataSnap classes and have not found code that would cause this effect, which is leading me to think that this behavior may originate in the browser or IIS.
Why is OnUserAuthenticate being called twice upon the first server method call, and what is producing this effect?
In response to Mat DeLong's answer, I am showing one of generic functions I am using to call my server methods. This particular method makes a synchronous call. The baseURL parameter normally is a string similar to http://mydomain.com/myservice/myservice.dll/datasnap/rest/tservermethods1, and method might be myservermethod. Additional parameters are used to pass parameters to the server method:
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);
}
Edit: Lex Li appears to be correct, that IIS is only using basic authentication if it fails on the first request. Also, Mat is correct that the OnUserAuthenticate should only be called once per session. After examining the link he provided, it was apparent that I was failing to capture and return the session id. Here is an example of code that works, this time asychronously. It captures the sessionid and stores it in a hidden field. It then passes that session id back in each subsequent call in a Pragma header.
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 < 3开发者_运维技巧00)
{
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);
}
Edit: When I first posted these two code samples I included sample usernames and passwords in the fourth and fifth parameters of the XMLHttpRequest open method. I was passing these values explicitly during testing. Passing the parameters like this is not recommended, so I removed those parameters in this edit. If you omit these passwords, and don't pass them using another methods (such as in the header of your first REST server method invocation) the browser will display a dialog box asking the user for this information. Once the username and password has been supplied, the browser will remember this information for the remainder of the session. If you close and then reopen the browser and invoke another REST server method, you will once again be challenged for the username and password. This assumes, of course, that you are rejecting invalid users through the OnUserAuthenticate event handler.
Well, that's a typical IIS behavior for other technologies, such as ASP.NET.
The initial request does not contain your user credentials, and triggers a 401 response. Then the second request does send out your user credentials, then IIS can return 200.
Not sure if this applies to DataSnap, but if you capture network packets, you may find it out.
http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate.aspx
I can't say for sure why you are seeing this behaviour without seeing your server code, and the exact JavaScript you are using with the XHR. OnUserAuthenticate is intended to be called once ever, for a session.
You can see the XE DataSnap REST protocol here: http://docwiki.embarcadero.com/RADStudio/en/DataSnap_REST_Messaging_Protocol#Session_Information
It is intended that the first call returns a session ID, and all future calls are supposed to specify that. If this is done, all calls but the first use the session ID given to skip the authentication process and only go to the OnUserAuthorize event, if one is assigned.
If you are seeing OnUserAuthenticate called twice, that is likely a bug unless it is something weird in the XHR, or IIS configuration.
If you could provide a bit more detail I'd be happy to take a closer look.
精彩评论