You are not logged in.
Pages: 1
I'm fairly new to mORMot, so please bear with me if my question seems stupid.
I'm using THttpApiServer to build an API server, and I found out that if I make a response for a long time (e.g. Sleep(3000) in the OnProcess callback), the follow-up requests will blocking.
So does THttpApiServer is a single-threaded server? If it is, what should I do if I need to handle multiple requests in a moment?
I'm not good at English, sorry for that...
Thanks in advance!
Offline
Thanks for your reply!
I wrote a simple console application to test it.
program TestHttpApiServer;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, SynCommons, SynCrtSock, SynLog;
type
TMyServer = class
constructor Create;
destructor Destroy; override;
private
fServer: THttpApiServer;
function Process(Ctxt: THttpServerRequest): Cardinal;
public
procedure Start;
end;
var
ILog: ISynLog;
Server: TMyServer;
{ TMyServer }
constructor TMyServer.Create;
begin
fServer := THttpApiServer.Create(True);
fServer.OnRequest := Process;
fServer.AddUrl('root', '8080', False, '+', True);
end;
destructor TMyServer.Destroy;
begin
fServer.Free;
inherited;
end;
function TMyServer.Process(Ctxt: THttpServerRequest): Cardinal;
begin
ILog.Log(sllDebug, '% : % %', [NowUTCToString, Ctxt.Method, Ctxt.URL]);
Sleep(3000);
ILog.Log(sllDebug, '% : % % => %', [NowUTCToString, Ctxt.Method, Ctxt.URL, 'OK']);
Ctxt.OutContent := 'OK';
Result := 200;
end;
procedure TMyServer.Start;
begin
fServer.Start;
ILog.Log(sllInfo, ': server start!');
end;
begin
with TSynLog.Family do
begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE;
PerThreadLog := ptOneFilePerThread;
end;
ILog := TSynLog.Add;
Server := TMyServer.Create;
Server.Start;
Readln;
end.
Could you please give me some advice?
Offline
No, it uses a thread pool, of 32 threads by default.
I guess the blocking is on the client side.
As the simple program above, I use a test-tool named ab (ApacheBench) to test it, the option -c5 means to use five requests to make at a time.
C:\Users\user>ab -n10 -c5 http://127.0.0.1:8080/root
This is ApacheBench, Version 2.3 <$Revision: 1663405 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient).....done
Server Software: mORMot
Server Hostname: 127.0.0.1
Server Port: 8080
Document Path: /root
Document Length: 2 bytes
Concurrency Level: 5
Time taken for tests: 30.011 seconds
Complete requests: 10
Failed requests: 0
Total transferred: 1840 bytes
HTML transferred: 20 bytes
Requests per second: 0.33 [#/sec] (mean)
Time per request: 15005.250 [ms] (mean)
Time per request: 3001.050 [ms] (mean, across all concurrent requests)
Transfer rate: 0.06 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 1 0.4 1 2
Processing: 3002 12000 4472.0 15001 15002
Waiting: 3002 12000 4472.0 15001 15002
Total: 3003 12002 4471.7 15002 15002
Percentage of the requests served within a certain time (ms)
50% 15002
66% 15002
75% 15002
80% 15002
90% 15002
95% 15002
98% 15002
99% 15002
100% 15002 (longest request)
C:\Users\user>
And the program output as following:
20180308 02325142 info : server start!
20180308 02325428 debug 2018-03-08 02:32:54 : GET /root
20180308 02325728 debug 2018-03-08 02:32:57 : GET /root => OK
20180308 02325728 debug 2018-03-08 02:32:57 : GET /root
20180308 02330028 debug 2018-03-08 02:33:00 : GET /root => OK
20180308 02330028 debug 2018-03-08 02:33:00 : GET /root
20180308 02330328 debug 2018-03-08 02:33:03 : GET /root => OK
20180308 02330328 debug 2018-03-08 02:33:03 : GET /root
20180308 02330628 debug 2018-03-08 02:33:06 : GET /root => OK
20180308 02330628 debug 2018-03-08 02:33:06 : GET /root
20180308 02330928 debug 2018-03-08 02:33:09 : GET /root => OK
20180308 02330928 debug 2018-03-08 02:33:09 : GET /root
20180308 02331228 debug 2018-03-08 02:33:12 : GET /root => OK
20180308 02331228 debug 2018-03-08 02:33:12 : GET /root
20180308 02331528 debug 2018-03-08 02:33:15 : GET /root => OK
20180308 02331528 debug 2018-03-08 02:33:15 : GET /root
20180308 02331828 debug 2018-03-08 02:33:18 : GET /root => OK
20180308 02331828 debug 2018-03-08 02:33:18 : GET /root
20180308 02332128 debug 2018-03-08 02:33:21 : GET /root => OK
20180308 02332128 debug 2018-03-08 02:33:21 : GET /root
20180308 02332428 debug 2018-03-08 02:33:24 : GET /root => OK
It's obvious that the requests were processed one-by-one.
What should I do? Thanks a lot!
Offline
You forgot to call Clone().
fServer.AddUrl('root', '8080', False, '+', True);
fServer.Clone(32);
I will fix sample 9 and 13 code.
Sample 22 was correct, in that matter.
But I would not use low-level THttpApiServer, anyway, but higher level TSQLHttpServer, which is cross-platform and has additional features.
Offline
Also note that your use of TSynLog is plain wrong.
Each TSynLog instance should be used in a single thread, locally.
You should use it as such:
procedure TMyServer.Start;
begin
fServer.Start;
TSynLog.Add.Log(sllInfo, ': server start!');
end;
Or in a single method:
function TMyServer.Process(Ctxt: THttpServerRequest): Cardinal;
var
log: TSynLog; // or ISynLog if you use Enter
begin
log := TSynLog.Add; // or := TSynLog.Enter for ISynLog local variable
log.Log(sllDebug, '% : % %', [NowUTCToString, Ctxt.Method, Ctxt.URL]);
Sleep(3000);
log.Log(sllDebug, '% : % % => %', [NowUTCToString, Ctxt.Method, Ctxt.URL, 'OK']);
Ctxt.OutContent := 'OK';
Result := 200;
end;
Offline
Thank you very much! fServer.Clone() works great!
I have considered TSQLHttpServer, but it takes more learning.
According to my current understanding, it seems that TSQLHttpServer needs to work with a TSQLRestServer together,
and the TSQLRestServer needs to work with TSQLRecord which needs to associate database tables (and record columns with published properties) exactly.
But my project have plenty of legacy stored procedures. e.g. a GetUserLoginInfo procedure accepts a username and password then do validation and logs to an authorization log table internally, this procedure returns an multi-table joined query result (not only the user table stuffs). So maybe I could not declare a TSQLRecord based TUser class exactly maps to existed User table, since the result of GetUserLoginInfo is mixed with multi-tables fields. I don't know how to fetch an multi-table joined query result into inter-related TSQLRecord objects till now.
Please forgive my poor understanding and description...
Your documents is a great, it talks about not only the use of framework classes, but also advanced software architecture ideology.
So it deserves repeated readings, but it takes a lot of time for me, I wish there will be more practical examples.
Thanks to all who work for the great mORMot framework!
Last edited by DDGG (2018-03-09 04:04:43)
Offline
Also note that your use of TSynLog is plain wrong.
Each TSynLog instance should be used in a single thread, locally.
...
I will use it in accordance with your instructions, thanks!
BTW: I saw the implementation of the TSynLog.Add, it seems not thread safe, is it?
class function TSynLog.Add: TSynLog;
{$ifdef HASINLINENOTX86}
var f: TSynLogFamily;
begin
if Self<>nil then begin // inlined TSynLog.Family (Add is already inlined)
f := PPointer(PtrInt(Self)+vmtAutoTable)^;
if f=nil then
result := FamilyCreate.SynLog else
result := f.SynLog;
end else
result := nil;
end;
{$else}
asm
call TSynLog.Family
jmp TSynLogFamily.SynLog
end;
Thanks.
Offline
@DDGG, I believe your situation perfectly fits in the use case of TSQLRestServer, please search for 'method-based service' and 'interface-based serfice' in the mORMot document, that's the correct approach for implementing your GetUserLoginInfo API.
and you can use an in-memory db (use ':memory:' as the db file name) as the database, then just define a single dummy TSQLRecord-derived class in order for the framework to work.
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
TSynLog.Add is thread-safe since to enable logging you need to access the TSynLog.Family at least once.
It is perfectly thread-safe to write in the main thread, and read in several threads.
Do not be too much dubious: the framework is used on production since years on heavily multi-threaded apps.
Offline
@edwinsn Although I don’t quite understand you said, I'm still grateful!
@ab I seem to understand: Because TSynLog.Family has been called in the main thread, so when TSynLog.Add called in TMyServer.Process with multi-threads it just simply execute result := f.SynLog; (it is a Singleton design pattern). is it?
Thanks.
I don't doubt the framework's multithreaded security design, just worry that I'm using it incorrectly...
Offline
@edwinsn Although I don’t quite understand you said, I'm still grateful!
If you want to expose from the server-side to the client-side your stored procedure such as GetUserLoginInfo you mentioned, you have two high-level "mORMot approaches":
1 - use method-based service, or
2 - use interface-base service.
For approach #1, you can take a look at https://github.com/synopse/mORMot/blob/ … Server.dpr
Code like the following will be much much more easier and intuitive than doing everything in THttpApiServer.OnRequest.
type
// TSQLRestServerFullMemory kind of server is light and enough for our purpose
TServiceServer = class(TSQLRestServerFullMemory)
published
procedure Sum(Ctxt: TSQLRestServerURIContext);
end;
See that? You don't even need the TSQLRecord class or the SQLITE engine.
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
For approach #1, you can take a look at https://github.com/synopse/mORMot/blob/ … Server.dpr
See that? You don't even need the TSQLRecord class or the SQLITE engine.
Thanks for your detailed instructions, I have learned the sample you mentioned.
Do you mean I need to re-implement a GetUserLoginInfo service by Delphi code? No, I just want to re-use those legacy stored procedure in database.
So I'm writing a service class and some methods simply call the underlying stored procedure (e.g. GetUserLoginInfo).
I'm going to convert the stored procedure's result from variant to object for temporary storage and processing, and convert it to JSON for a HTTP response. (I know there is a FetchAllAsJSON but I do sure need an object)
Offline
No, I didn't mean you need to re-implement/duplicate the code logic from the stored procedure, you just call it from within the service class. What I mean is that, if I understand you correctly you don't have to manually do the following:
"I'm going to convert the stored procedure's result from variant to object for temporary storage and processing, and convert it to JSON for a HTTP response. (I know there is a FetchAllAsJSON but I do sure need an object)"
if you use TSQLRestServer instead.
Last edited by edwinsn (2018-03-09 12:45:32)
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
No, I didn't mean you need to re-implement/duplicate the code logic from the stored procedure, you just call it from within the service class. What I mean is that, if I understand you correctly you don't have to manually do the following:
...
if you use TSQLRestServer instead.
Okay, I will continue learning, thank you!
Offline
Pages: 1