You are not logged in.
1. I would rather use a class than a record as parameter.
That would be more rich, but involves more extensive changes. The record is just a way of repackaging the existing stack-based variables, so it has no overhead and is automatically managed.
We do not have to forget that SynCrtSock has two HTTP servers
Didn't test the second one (that's what we already had, so I was more interested in http.sys), though I made the changes to record to keep the unit in compiling state.
I suspect even the 2nd one is very efficient (more than Indy, by design).
The one we have on sockets + thread pool is within a few percents of IIS & Apache & THttpAPIServer, in practice, above a certain performance level, I suppose the sockets API and Windows networking stack are more a bottlenecks than the http server is.
2. See how function CompressDataAndGetHeaders() is implemented:
it will compress only TEXT/.... or APPLICATION/JSON... or APPLICATION/XML... content.
[...]So I do not understand your concern about png/jpg for instance.
Hmm, didn't spot that, I just assumed from the name it was compressing everything, maybe it should be renamed to CompressTextDataAndGetHeaders() to make it more obvious and one doesn't have to look at the implementations?
For mORMot, i.e. pure Delphi client, we prefer using our SynLZ compression scheme
Been using LZRW1 for that here, any idea how it compares? (I'll probably give it a whirl, there is a lzrwk1h unit hanging in the Delphi world, which we used a long time ago, but the implementation was fully rewritten since)
There is improvement possible, here, for sure, but only for static content I suspect.
Would mostly be for static html, js & css. The js & css files are typically minified and aggregated server-side here, so each page ideally has only one js and one css file linked in.
Server-side, the js & css files are non-minified, commented versions. This allows to switch to client-side debuggable js with a simple switch that turns off the minification+aggregation.
At the moment I'm experimenting with a "repackaged" Process method, the strings moved to records:
TSynHttpServerRequest = record
InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
ConnectionID : Int64;
end;
TSynHttpServerResponse = record
OutContent, OutContentType, OutCustomHeader: RawByteString;
end;
TOnHttpServerRequest = function(
const inRequest : TSynHttpServerRequest;
var outResponse : TSynHttpServerResponse ) : cardinal of object;The call overhead is non-measurably lower, but having records will open up adding methods for common stuff (f.i. add standard expiration/last-modified headers, etc.)
Also, currently the compression is set for the whole server, but a lot of data is already compressed (such as png or jpg images), so maybe it could be a boolean flag in the response instead?
Other data could benefit from the deflate being cached on the server-side (thus returning pre-compressed content, rather than re-compressing each time).
I added some test code to your project. Periodically in the log appears messages of wrong base64-encoded lines [...]
Darn! Thanks for looking it up!
I found the issue: the headers parser was applying UrlDecode, which occasionally turned a '+' in the base64 encoding to a ' ', and since ' ' is allowed (and ignored) by base64 coders, there was no error thrown there... *feels like a fool* as I thought base64 was immune to UTF8 & encoding issues, which it just wasn't ![]()
@ab: any chance of surfacing the already parsed headers array in SynCrtSock? This would have allowed this mistake, and avoided the double-parsing
In the code above, you you are using NTLM security package for authentication, and Negotiate is used in module SysSSPIAuth.pas (const SecPkgName = 'Negotiate').
You can use Negotiate as well (for Kerberos), but that requires setting up SPN & other site prefixes, which complicates testing the code.
SSPI supports both, I've tried boths, and have the same issue of connection randomly failing in the same conditions, ie. they (AFAICT) never fail for local connections or when a breakpoint is set in the IDE, but have about 50% chance of succeeding when connecting from other machines on the LAN and there is no debugger breakpoints set.
This is so very weird (especially the debugger breakpoint thing), I'm wondering if the issue isn't with the domain controllers here: if one of the controllers were misconfigured in some fashion, it would explain the 50% success rate. Though it wouldn't explain the breakpoints thing or why we don't see the issue from Windows... (unless the OS were to silently fallback).
I guess if someone else tests and can't reproduce my issues, then that'll definitely point the finger at the domain here.
Below is a test app based on the web server, to compile it you should need to expand the Process method to pass the connectionId.
other units are from the DWS svn
program WWWAuth;
{$APPTYPE CONSOLE}
uses
SysUtils,
SynCommons,
SynCrtSock,
dwsUtils,
dwsWebEnvironment,
dwsSynopseWebEnv,
SynSSPIAuth;
type
TNTLMAuth = class
SecContext : TSecContext;
User : RawUTF8;
end;
TTestServer = class
protected
FServer : THttpApiServer;
public
constructor Create(const basePath : TFileName);
destructor Destroy; override;
function Process(const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
out OutContent, OutContentType, OutCustomHeader: RawByteString;
connectionId : Int64) : cardinal;
end;
var
vPendingAuth : TFastCompareStringList;
vPendingAuthCS : TFixedCriticalSection;
{ TTestServer }
constructor TTestServer.Create(const basePath : TFileName);
begin
FServer:=THttpApiServer.Create(false);
FServer.AddUrl('', '888', false,'+');
// FServer.RegisterCompress(CompressDeflate); // our server will deflate html :)
FServer.OnRequest:=Process;
end;
destructor TTestServer.Destroy;
begin
FServer.Free;
inherited;
end;
{$WARN SYMBOL_PLATFORM OFF}
function TTestServer.Process(
const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
out OutContent, OutContentType, OutCustomHeader: RawByteString;
connectionId : Int64): cardinal;
var
p : Integer;
request : TSynopseWebRequest;
auth : TNTLMAuth;
auth64 : String;
begin
request:=TSynopseWebRequest.Create;
try
request.InURL:=InURL;
request.InMethod:=InMethod;
request.InHeaders:=InHeaders;
request.InContent:=InContent;
request.InContentType:=InContentType;
vPendingAuthCS.Enter;
try
p:=vPendingAuth.IndexOf(IntToHex(connectionId, 16));
if p>=0 then begin
auth:=TNTLMAuth(vPendingAuth.Objects[p]);
end else begin
auth:=TNTLMAuth.Create;
InvalidateSecContext(auth.SecContext);
vPendingAuth.AddObject(IntToHex(connectionId, 16), auth);
end;
finally
vPendingAuthCS.Leave;
end;
if auth.User='' then begin
auth64:=request.Header('Authorization');
if auth64='' then begin
OutCustomHeader:='WWW-Authenticate: NTLM'#13#10;
OutContentType:=HTML_CONTENT_TYPE;
OutContent:='<HTML><BODY>401 Unauthorized.</BODY></HTML>';
Result:=401;
Exit;
end else if ServerSSPIAuth(auth.SecContext, Base64Decode(RawByteString(StrAfterChar(auth64, ' '))), OutContent) then begin
OutCustomHeader:='WWW-Authenticate: NTLM '+BinToBase64(OutContent)+#13#10;
OutContentType:=HTML_CONTENT_TYPE;
OutContent:='<HTML><BODY>401 Unauthorized.</BODY></HTML>';
Result:=401;
Exit;
end;
ServerSSPIAuthUser(auth.SecContext, auth.User);
end;
OutContent:='<html><body>Logged on as '+auth.User+'</body></html>';
OutContentType:=HTML_CONTENT_TYPE;
Result:=200;
finally
request.Free;
end;
end;
begin
vPendingAuth:=TFastCompareStringList.Create;
vPendingAuthCS:=TFixedCriticalSection.Create;
with TTestServer.Create('') do try
write('Server is now running on http://localhost:888/'#13#10#13#10+
'Press [Enter] to quit');
readln;
finally
Free;
end;
end.Did you check http://synopse.info/forum/viewtopic.php?pid=5809#p5809 ?
Unless I'm mistaken this is an URI-based scheme? ie. the user info needs to be entered or saved on the client-side in some fashion.
WWW-Authenticate allow that to be resolved by the browser from OS user session info, besides single-sign-on convenience, it can also be made a bit more secure in a LAN since OS logons can be more richly administered, can be tied to biometric systems, etc. all things which would be problematic (or time consuming) to reproduce in a web browser.
Besides, it also means there is no need to have user administration in the web app.
Is NTLM required after WWW-Authenticate: ?
Yes, see http://www.innovation.ch/personal/ronald/ntlm.html
For Kerberos, the sequence is the same with "Negotiate" in place of "NTLM". That's more secure but requires setting up an SPN, which is overkill when doing the auth inside a LAN or over a VPN.
I've added that to the SynopseWebServer demo in DWS
uses dwsUtils;
type
TNTLMAuth = class
SecContext : TSecContext;
User : RawUTF8;
end;
var
vPendingAuth : TFastCompareStringList;
vPendingAuthCS : TFixedCriticalSection;
// and in Process method
vPendingAuthCS.Enter;
try
p:=vPendingAuth.IndexOf(IntToHex(connectionId, 16));
if p>=0 then begin
auth:=TNTLMAuth(vPendingAuth.Objects[p]);
end else begin
auth:=TNTLMAuth.Create;
InvalidateSecContext(auth.SecContext);
vPendingAuth.AddObject(IntToHex(connectionId, 16), auth);
end;
finally
vPendingAuthCS.Leave;
end;
if auth.User='' then begin
auth64:=request.Header('Authorization');
if auth64='' then begin
OutCustomHeader:='WWW-Authenticate: NTLM'#13#10;
OutContentType:=HTML_CONTENT_TYPE;
OutContent:='<HTML><BODY>401 Unauthorized.</BODY></HTML>';
Result:=401;
Exit;
end else if ServerSSPIAuth(auth.SecContext, RawDecode64(RawByteString(StrAfterChar(auth64, ' '))), OutContent) then begin
OutCustomHeader:='WWW-Authenticate: NTLM '+BinToBase64(OutContent)+#13#10;
Result:=401;
Exit;
end;
ServerSSPIAuthUser(auth.SecContext, auth.User);
end;
OutContent:=auth.User;To the Process method I just added a connectionId parameter, which takes the Req^.ConnectionId for the http.sys server, and a Int64(Socket) for the thread/socket-based one.
RawDecode64 is just an alternate base64 decoder I tried, but results are the same as with the Synopse one.
Asked on stack overflow as well
http://stackoverflow.com/questions/1372 … gon-denied
I'm experimenting with single-sign on using www-auth and ServerSSPIAuth, which involves challenge-response on an http connection.
I've hacked Request() to pass the ConnectionID so I can track the connection (that parts works fine)
However, I'm getting random logon failed in ServerSSPIAuth when the server runs, but if I have a breakpoint in the IDE before the call to ServerSSPIAuth, which effectively imposes a delay, then the authentication always succeeds AFAICT.
AcceptSecurityContext and the rest of SSPI are supposed to be thread-safe, according to MS docs.
The issue can also happen when there is a single client attempting to connect (so it's not a simultaneous authentication issue)
Any ideas?
Using an opaque structure? I'll see what can be done. Perhaps the easier could be a "Sender" parameter.
Sender might not be opaque enough, an Int64 as the ConnectionId is probably enough, and in other classes, it could be a pointer cast to an integer, an integer counter, etc.
How do you retrieve the socket from this ConnectionId field?
I don't think you're supposed to, and you don't need to AFAIK, all that is needed is knowing if requests are coming down an already authenticated http connection, so the opaque ID is enough.
The connection close notification is to be able to release authentication resources asap rather than wait on a time out.
The missing elements are the ConnectionId field of the HTTP_REQUEST structure, and to be notified of a disconnect, one needs to call HttpWaitForDisconnect.
Also would it be possible in a future version of the SynCrtSock to use a record to pass the In/Out fields?
This would allow attaching extra fields (like ConnectionId or the pre-parsed headers), as well as simplify the call (only one pointer on the stack rather than the 8 ones currently required)
At the moment it seems the only way to access ConnectionID is to reimplement THttpApiServer.Execute in a subclass, but of course, that means any future fixes/improvements to THttpApiServer wouldn't be carried over.
At http.sys level, there is no direct/included NTLM authentication available, as stated by http://www.dupuis.me/node/22
I've used that one and http://www.innovation.ch/personal/ronald/ntlm.html to get it working.
However http ntlm authentication happens at the connection level, the problem is that SynCrtSock doesn't provide any connection-level data, so it works only for the first HTTP 1.1 connection from a client, and can't work when multiple clients are involved.
The authentication headers are send through only during the challenge phase, so after the challenge, there is no way to figure out in SynCrtSock if the request comes from an already authenticated connection or from another, not-yet authenticated connection.
So basically, I'm missing a tcp connection handle of some sort (could be any opaque number that uniquely identifies a connection at a given time), and a connection close notification (at which point the previous handle could be recycled) to have the http-level ntlm authentication work.
http-level authentication means you can have single sign-on with IE and Chrome browser (no need to enter user login or password), when the browser is running from an authenticated used session in the domain.
It looks like the function ServerSSPIAuth would be the key for that?
Got some of it working, however the Process event in THttpApiServer doesn't have connection information.
How do you know when a connection is lost or if a connection has already been authenticated?
AFAICT in mORMot the above is based on an ID and expiring cache, does it mean there is no way to track the connection at the HTTP level?
If so, does it mean NTLM should only be used to establish a connection cookie or something like that?
Then, you will need the mORMot core, i.e. mORMot.pas, and not only SynCrtSock.
These are not part of the stable release 7z (http://synopse.info/fossil/wiki?name=Downloads), is it normal?
Also is there a minimal SSPI demo? Something minimalistic above SynCrtSock, that would allow IE or Chromium to perform NTLM authentication.
It looks like the function ServerSSPIAuth would be the key for that?
So perhaps this is where mORMot classes may be useful...
You have a lot of stuff here, like ORM, DB access, logging, caching, interface-based services...
Indeed!
IME (wrapping classes for use at work), a lot of the time spent in exposing classes and function to scripts actually goes to the securing more than the wrapping, to ensure there are no leaks, that erroneous parameters don't result in AVs, etc.
Also basic things WebResponse/WebRequest in the demo currently use RTTI, but this is essentially a shortcut for the demo, as RTTI can quickly end up exposing things that are not safe at all.
That is, do not use Delphi classes, but interfaces for defining services, sessions to handle instance lifetime, and JSON/records/arrays for COW value objects.
I haven't really investigated what is available in mORMot so far, but I assume a lot of the work is already done!
Extending the demo to integrate as a service instead, or to have managed sessions, Windows-based authentications, etc. would be nice.
Currently the demo exposes only the internal functions, so this is fairly limited indeed.
Adding the JSON connector and a new restricted file I/O library should be fairly easy. There is also the COM connector, though this one would have to be optional since it breaks out of the sand-box in a rather unlimited fashion.
DB library is also a must, though that has to be developed.
And then there is everything else... so it's not a php competitor in terms of libraries just yet ![]()
One could also expand the RTTI environment to provide light-weight exposure of Delphi stuff, however that's problematic in that Delphi classes aren't garbage-collected and usually quite fragile, so it's easy to leak, create AV, etc. which isn't very desirable in a web server. This is also a reason why I haven't devoted too much time in DWS to RTTI, the Delphi RTL & VCL just isn't solid enough for abuse.
(once a file is cached, it stays in forever), and won't be purged if the underlying dws files are changed.
That limitation is now gone from the demo (added basic support for FindFirstChangeNotification to automatically flush the cache)
You can put a reference to http://synopse.info and the original sample source, like this.
Done!
I think I will add something similar to the official mORMot code.
What is to be used to refer to DWS project itself?
This should be enough
DelphiWebScript project is released under the Mozilla Public License v1.1
Source repository and downloads
http://code.google.com/p/dwscript/
Project website and blog
http://delphitools.info/dwscriptI suspect we may have much better performance, by maintaining an in-memory cache of the .dws scripts AST.
Is it difficult to implement?
It is already implemented in the demo ![]()
A limitation is that the cache currently doesn't expire, doesn't have memory use limitations (once a file is cached, it stays in forever), and won't be purged if the underlying dws files are changed.
Another limitation is that there is a single compiler instance, but that'll only matter during warmup of websites that have legions of large dws files accessed in random order.
Added SynopseWebServer demo to DWScript SVN.
Still pretty basic and missing tons of stuff, but works quite well with good perf already ![]()
I'll try to jazz it up in future versions to something usable as a "reference" for small production servers (ie. add some security, file I/O from DWS, stuff like that), so if you find mistakes & misuses, let me know.
Btw, is there any boiler plate comment header you want me to add in the headers of the units mentioning Synopse? a link to http://synopse.info or somewhere else? Or should it refer to mORMot instead?
What about just embracing SQL statement with [ ] since it should not appear after an assignment token in normal pascal?
But it's allowed in DWScript to have inline static arrays. ![]()
In Smart it's already used in WebGL to declare vectors and matrix inline.
vector := [1, somevar, 3];
matrix := [1, 0, 0,
0, 2, 0,
0, 0, 1];That said, another option could be to have an 'sql' keyword, for all sql statement that don't start with select, or requiring a db connection/object in front (which will probably be required anyway to support different db connection)
I thought you were expecting a new internal type, like sql or even a variant, just like automation.
Yes, but that's not enough for instance you could have:
var insert : TWhatever;
...
request := insert.GetTheSQL();
or
insert : sql;
...
request := insert;
If the language extension registers "insert" as a new keyword, the above would fail.
And while select isn't too common in Pascal (IME), insert, create, update & delete are quite common.
It could be a good idea to use all SQL commands, including INSERT/UPDATE/DELETE...
Yes, though a limiting factor would be how to distinguish an SQL query from regular Pascal code.
F.i. "CREATE" would awfully look like a class constructor to the parser.
A simple language extension can react on a keyword, if you've got to parse several terms to figure out what's happening, things become quite a lot more complicated.
So to keep things simple, some more complex start marker/keyword would be needed.
Had look at HttpApiServer demo, now I've a couple questions to ask:
1) If I understood correctly, the Request method is invoked from multiple threads at the same time, and those threads are from ServerThreadPoolCount ones? What does happen when the thread pool is exhausted? are new threads created on the fly or is there a backlog?
2) the Request methods takes raw http headers, however at the THttpSocket level, headers seem to have been already parsed. If there a standard way to get those pre-parsed headers instead? (to avoid re-parsing them)
3) Are there some url-encoded encoding/decoding routines? (not too complex, but might as well not replicate the effort) oops, saw them
4) Is there already multi-part/content-encoding parser for the request? (thinking of posted forms, might as well not replicate the effort for that)
Also, but that's beyond the scope of the demo, but is there already some form of URL rewriting and/or request dispatch API?
(thinking of being able to have a single virtual, url-rewritten web server serve DWScript, static files, and other mORMot services on the same port)
Including SQL statements within DWS language, including strong typing and such, would need a full SQL parser embedded within DWS.
Actually I hope to get away with a "stupid" parser that would leverage the full SQL parser of SQLite, something like:
start "parsing" sql when encountering "select"
grab everything up to the end of the statement, noting variable references and expression in passing (maybe prefixed with a '@'), and replace them with parameters
feed the SQL to SQLite, have it prepare the query, report errors
assume sqlite3_column_decltype is meaningful enough to be used for strong typing
Of course that means the compiler must have access to the db at compile time (which is at runtime anyway, for a script), and that may not work out when connecting to arbitrary databases. But for dedicated web servers, that limitation should be okay in many cases.
And it would still be possible to compile all script at the web server startup, and thus "statically" validate everything.
And yes, I'm already using virtual tables like you to make queries across different databases. When worse comes to worse, the virtual tables can still be used as a way to conveniently filter and import a temporary table in SQLite, index it and get back the full speed.
Even if ORM tries to avoid writing SQL, and compute it for you...
Yep, but sometimes good old fashionned SQL beats OO in terms of expressiveness. I find OO is practical for browsing & updating data, but SQL is better for joining/filtering "en masse" or spitting out reports/statistics.
I don't find LINQ f.i. to be much neater than the corresponding SQL would be IMHO. I've seen several articles caliming the opposite, but the examples were typically twisted and the SQL looked like auto-generated SQL (like f.i. http://www.linqpad.net/WhyLINQBeatsSQL.aspx)
There is another thing I would like to give a try to: exposing an array of objects or records (in memory) as transient SQLite virtual tables.
This would allow things like (using TPoint just for illustration, the query is meaningless)
a1 : array of TPoint;
a2 : array of TPoint;
...
result := (select a1.x, a2.y
from a1 left join a2 on (a1.x=a2.x)
where a1.y<a2.y).ToArray(TPoint);
performance might not always be extraordinary, but you could express wicked things, and the work required to make the above would be reasonnable given that SQLite would be doing all the hard stuff.
1. Use http.sys kernel mode high performance server to serve static and DWS-generated content.
[...]
4. Includes DWS as server-side scripting engine.
Yes those are the two early aspects I was after.
We have an http server tech DWScript runs on here (at work), but it's proprietary -- hence the lack of webserver demos in DWS repository, which is a bit ironical for a script engine with "Web" in its name, web-server roots, etc. ;-)
I started sometime ago a demo on Indy (to provide the open-source http server), but I don't use or know Indy much, and the performance wasn't so good, so I never really bothered finishing the demo.
3. Expose some mORMot methods to the DWS engine, like ORM, interface-based services, and so on.
Yep, that could be a second step.
Using DWScript connectors and language extension mechanisms, it should be possible to expose many things in various ways.
One simple language extension I had in mind was being able to have inline SQL. Assuming the DB schema is known at script compile time, something like that could be supported, with full compile-time syntax-checks & strong typing, both for the Pascal & the SQL
filter := 123;
cursor := select whatever from table where field = @filter;
while not cursor.Eof do begin
...
cursor.Next;
end;
Eric
Just asking, is there already some code on integrating DWScript with the mORMot HTTP server?
Like some basic exposure for HTTP requests & response fields, in the case of a simple file-server with some dws processed pages alongside raw files.
Initially that would be for making a simple HTTP server demo for DWScript multi-threading & caching features.