Board index » delphi » HTTPServer and Threads - Basic, probably...

HTTPServer and Threads - Basic, probably...


2004-08-23 07:42:28 PM
delphi27
I have written an app that works 100% as single thread application.
However, I have now been told that the client may attempt to send a load of
requests at the same time. Up to 10.
So now, I need to make my app multithreaded, and not sure where to go. IU
see there's an AThread : TIdPeerThread parameter on most of the THTTPServer
(Indy) functions. However, I am not sure how to use these as I am new to
threads.
I think I need to create a TThread unit, and put all the work I currently
have in my IdHTTPServer1CommandGet function, but am not sure how this is
done, and also, how I pass all the parameters to this new thread function.
Also, I am not sure how the response would be delt with.
AThread: TIdPeerThread; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo:
TIdHTTPResponseInfo);
Those are the parameters with the IdHTTPServer1CommandGet function. Could
anyone help me out?
 
 

Re:HTTPServer and Threads - Basic, probably...

Quote
So now, I need to make my app multithreaded, and not sure where to go. IU
see there's an AThread : TIdPeerThread parameter on most of the
THTTPServer
(Indy) functions. However, I am not sure how to use these as I am new to
threads.
?? TidHTTPServer is designed as thread-blocking with one thread per socket.
If a client opens ten connections, then the server will use ten threads to
handle all ten sockets.
Rgds,
Martin
 

Re:HTTPServer and Threads - Basic, probably...

Thanks Martin. So the component is built for thread handling, I see.
My problem then is that I have all my code in:
procedure TForm1.HTTPServerCommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
And I have no reference to AThread.
This works fine for one request, but sending a few requests at the same time
causes error 500.
It seems like the code that handles the request needs to be some type of
thread object?
"Martin James" <XXXX@XXXXX.COM>writes
Quote

>So now, I need to make my app multithreaded, and not sure where to go.
IU
>see there's an AThread : TIdPeerThread parameter on most of the
THTTPServer
>(Indy) functions. However, I am not sure how to use these as I am new to
>threads.

?? TidHTTPServer is designed as thread-blocking with one thread per
socket.
If a client opens ten connections, then the server will use ten threads to
handle all ten sockets.

Rgds,
Martin




 

Re:HTTPServer and Threads - Basic, probably...

Quote
My problem then is that I have all my code in:

procedure TForm1.HTTPServerCommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);

And I have no reference to AThread.
All the code in this handler is called by the peer thread that is passed.
This means it could, (and will), be interrupted & reeentred from another
client-server peer thread at any time. The data accessed in there must be
thread-safe - no unprotected globals, no VCL etc - the usual.
Quote
This works fine for one request, but sending a few requests at the same
time
causes error 500.
I seem to remenber that 500 is 'Internal server error'. I do not know what
might cause this error to be generated by Indy. Perhaps an unexpected
exception in HTTPServerCommandGet?? You could try this, I guess, by
triggering one, eg. make a 'divide-by-zero' in your method & see waht
happens.
Quote
It seems like the code that handles the request needs to be some type of
thread object?
The code is already called by a per-client peer thread, as explained above.
Rgds,
Martin
 

Re:HTTPServer and Threads - Basic, probably...

Thanks again.
On connect, is there not something that has to be done with AThread? Surely,
I need a list of threads stored somewhere? I see there is a ThreadMgr that I
am not using. Does that not need to be set?
The only work that gets done in CommandGet is that it does a bit of DB work
(Reading and writing) and also checks the incoming XML against an XSD file.
It writes to a Memo a few times too, just to show the progress of the
transaction.
Nowehere in my current code do I mention a Thread, and I think it has to be
done. When a connect with one client, all works fine. When I connect with
two at exactly the same time, one gives an 'Error 500', which is correct,
because something is blowing out in the code.
My onConnect simply has :
procedure TForm1.HTTPServerConnect(AThread: TIdPeerThread);
begin
StatusBar1.SimpleText := 'Server Connected';
end;
And I think that there, a new thread somehow has to be started to handle the
chitchat between my server and the client.
Is this not required?
"Martin James" <XXXX@XXXXX.COM>writes
Quote

>My problem then is that I have all my code in:
>
>procedure TForm1.HTTPServerCommandGet(AThread: TIdPeerThread;
>ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
>
>And I have no reference to AThread.

All the code in this handler is called by the peer thread that is passed.
This means it could, (and will), be interrupted & reeentred from another
client-server peer thread at any time. The data accessed in there must be
thread-safe - no unprotected globals, no VCL etc - the usual.

>This works fine for one request, but sending a few requests at the same
time
>causes error 500.

I seem to remenber that 500 is 'Internal server error'. I do not know
what
might cause this error to be generated by Indy. Perhaps an unexpected
exception in HTTPServerCommandGet?? You could try this, I guess, by
triggering one, eg. make a 'divide-by-zero' in your method & see waht
happens.

>It seems like the code that handles the request needs to be some type of
>thread object?

The code is already called by a per-client peer thread, as explained
above.

Rgds,
Martin


 

Re:HTTPServer and Threads - Basic, probably...

Martin James writes:
Craig, In addition to what Martin says...
Quote
The data accessed in there must be
thread-safe - no unprotected globals
A good way of doing this is to use your own per-thread session object.
* Create an instance of your own class, containing whatever data you
need, during the OnConnect handler, and assign it to AThread.Data
* In the OnGet handler, cast AThread.Data back to your session object
so you can use it to get the per-thread data.
You need to destroy the session object when the thread disconnects. In
general, Indy does that for you. If you need more control, destroy it
yourself in the OnDisconnect handler.
--
Colin - using XanaNews HTTP Transport
e-mail :XXXX@XXXXX.COM
web: www.wilsonc.demon.co.uk/delphi.htm
Posted with XanaNews 1.16.4.2
 

Re:HTTPServer and Threads - Basic, probably...

Quote
A good way of doing this is to use your own per-thread session object.
TidHTTPServer already has a session object stored in the connection.data
Quote
* Create an instance of your own class, containing whatever data you
need, during the OnConnect handler, and assign it to AThread.Data
I suspect that you would have to derive from TidHTTPSession, or whatever it
is called, to add extra user state data. That or subclass TidPeerThread to
add in extra fields.
Rgds,
Martin
 

Re:HTTPServer and Threads - Basic, probably...

What I have done, which I didn't have before, is created an Object. I never
had objects before. Simply had all the code in the CommandGet handler.
Then, on CommandGet I create the object and pass it the variables I need.
(Does that sounds OK so far?)
Because my app logs to a DB alot, and the DB is Paradox (Yuk! I know), I
commented out my DB code. And hey presto! It seems to work!
On adding back the DB code, I am back to errors. This could be 1 (or 2) of 2
possible problems.
The code to generate the PK for the table is simply a Get
max(Record_Number).
Now, because 2 objects are trying that, it could be causing a key violation.
I think that is the highest possibility here.
Or else, because the DB access is out, the app is much faster, and because
of this, my clicking on the 2 clients isn't accurate enough, and it is not
really handling 2 sessions at once.
"Martin James" <XXXX@XXXXX.COM>writes
Quote

>A good way of doing this is to use your own per-thread session object.

TidHTTPServer already has a session object stored in the connection.data

>* Create an instance of your own class, containing whatever data you
>need, during the OnConnect handler, and assign it to AThread.Data

I suspect that you would have to derive from TidHTTPSession, or whatever
it
is called, to add extra user state data. That or subclass TidPeerThread
to
add in extra fields.

Rgds,
Martin




 

Re:HTTPServer and Threads - Basic, probably...

Quote

On connect, is there not something that has to be done with AThread?
Probably. Perhaps you need to open a database connection here to handle the
queries from the client. Each peer thread needs a database connection of
its own in order to perform DB opeerations in a safe manner. If there are a
lot of clients, this often means connection pooling to prevent the DB from
exceeding its own connection limits.
Quote
Surely,
I need a list of threads stored somewhere?
TidHTTPServer already has a list of threads, should you need them. Why
would you need them?
Quote
I see there is a ThreadMgr that I
am not using. Does that not need to be set?
Not particularly, there is a default Mgr.
Quote
The only work that gets done in CommandGet is that it does a bit of DB
work
(Reading and writing) and also checks the incoming XML against an XSD
file.
It writes to a Memo a few times too, just to show the progress of the
transaction.
You cannot write to a memo directly in here. VCL is not inherently
threadsafe. What would you expect to happen if, half-way though writing
text into the memo component, (changing internal pointers etc), your peer
thread is preempted & another client peer thread attempts to write to the
memo?
Quote
Nowehere in my current code do I mention a Thread, and I think it has to
be
done.
Your current code is called by a seperate thread per client connection,
that's why the peer thread is passed in - so that per-connection context can
be stored between commands - see Colins post.
When a connect with one client, all works fine. When I connect with
Quote
two at exactly the same time, one gives an 'Error 500', which is correct,
because something is blowing out in the code.
If you write directly to memos, statusBars or other VCL components from the
peer thread context/s, I am not surprised.
Quote

My onConnect simply has :

procedure TForm1.HTTPServerConnect(AThread: TIdPeerThread);
begin

StatusBar1.SimpleText := 'Server Connected';

end;
I don't think you can do this. I am not 100% sure, but I suspect that this
event is calle dby the peer thread/s too, so, no direct VCL methods.
Quote
And I think that there, a new thread somehow has to be started to handle
the
chitchat between my server and the client.

Is this not required?

You are already in a thread per client connection. It may be that more
threads are required in order to implement your requirements, but not for
client-server command handling.
Rgds,
Martin
 

Re:HTTPServer and Threads - Basic, probably...

Martin James writes:
Quote
>A good way of doing this is to use your own per-thread session
>object.

TidHTTPServer already has a session object stored in the
connection.data

>* Create an instance of your own class, containing whatever data
>you need, during the OnConnect handler, and assign it to
>AThread.Data

I suspect that you would have to derive from TidHTTPSession, or
whatever it is called, to add extra user state data. That or
subclass TidPeerThread to add in extra fields.
Indy (at least Indy 9!) doesn't use TidThread.Data to persist the
session information. It maintains a (separate) list of sessions, and
it looks up a thread's particular session from a random 15 character
session cookie.
As far as I can, TidThread.Data is still free to use - unless I missed
something...
The point of using TidThread.Data is it works not just for HTTP in
Indy, but for all the other server protocols too.
--
Colin - using XanaNews HTTP Transport
e-mail :XXXX@XXXXX.COM
web: www.wilsonc.demon.co.uk/delphi.htm
Posted with XanaNews 1.16.4.2
 

Re:HTTPServer and Threads - Basic, probably...

Quote
What I have done, which I didn't have before, is created an Object. I
never
had objects before. Simply had all the code in the CommandGet handler.
If you are creating a context object, where are you reating it & where are
you storing the reference? Context is somewhat dubious in HTTP anyway since
the protocol connects & disconnects all the time, hence cookies etc. for
client-side context.
Quote

Then, on CommandGet I create the object and pass it the variables I need.

(Does that sounds OK so far?)
Not really. If you need a context object, you should create it at connect
time, use it in the onCommandGet, or whatever, & free it at disconnect.
Quote
Because my app logs to a DB alot, and the DB is Paradox (Yuk! I know), I
commented out my DB code. And hey presto! It seems to work!

On adding back the DB code, I am back to errors. This could be 1 (or 2) of
2
possible problems.

The code to generate the PK for the table is simply a Get
max(Record_Number).

Now, because 2 objects are trying that, it could be causing a key
violation.
Each peer thread needs its own DB connection. Trying to use one connection
in an unprotected manner is likely ot result in problems.
Quote
I think that is the highest possibility here.
If you have only one DB connection, then yes.
Rgds,
Martin
 

Re:HTTPServer and Threads - Basic, probably...

Quote
Indy (at least Indy 9!) doesn't use TidThread.Data to persist the
session information. It maintains a (separate) list of sessions, and
it looks up a thread's particular session from a random 15 character
session cookie.

As far as I can, TidThread.Data is still free to use - unless I missed
something...

The point of using TidThread.Data is it works not just for HTTP in
Indy, but for all the other server protocols too.

I stand corrected, (or sit in my office corrected).
Thanks,
Martin
 

Re:HTTPServer and Threads - Basic, probably...

Great, thanks.
All my DB work is done on a DataModule. Can this be created and be an
instance for each object?
At the moment, it is on my main form as:
var
dmMain: TdmMain;
However, if I could remove this, and somehow have it used only for each
object, that may solve the problem?
"Martin James" <XXXX@XXXXX.COM>writes
Quote
>What I have done, which I didn't have before, is created an Object. I
never
>had objects before. Simply had all the code in the CommandGet handler.

If you are creating a context object, where are you reating it & where are
you storing the reference? Context is somewhat dubious in HTTP anyway
since
the protocol connects & disconnects all the time, hence cookies etc. for
client-side context.

>
>Then, on CommandGet I create the object and pass it the variables I
need.
>
>(Does that sounds OK so far?)

Not really. If you need a context object, you should create it at connect
time, use it in the onCommandGet, or whatever, & free it at disconnect.

>Because my app logs to a DB alot, and the DB is Paradox (Yuk! I know), I
>commented out my DB code. And hey presto! It seems to work!
>
>On adding back the DB code, I am back to errors. This could be 1 (or 2)
of
2
>possible problems.
>
>The code to generate the PK for the table is simply a Get
>max(Record_Number).
>
>Now, because 2 objects are trying that, it could be causing a key
violation.

Each peer thread needs its own DB connection. Trying to use one
connection
in an unprotected manner is likely ot result in problems.

>I think that is the highest possibility here.

If you have only one DB connection, then yes.

Rgds,
Martin




 

Re:HTTPServer and Threads - Basic, probably...

Martin James writes:
Quote
Context is somewhat dubious in HTTP anyway since
the protocol connects & disconnects all the time, hence cookies etc.
for client-side context.
BTW Craig, Martin's right in this. There's an important distinction
between 'HTTP sessions' and 'Connections', and often you have to handle
them separately.
For instance 'session cookies' should be kept for an entire HTTP
session. But other things - like NTLM authentication information are
strictly 'per connection'
--
Colin - using XanaNews HTTP Transport
e-mail :XXXX@XXXXX.COM
web: www.wilsonc.demon.co.uk/delphi.htm
Posted with XanaNews 1.16.4.2
 

Re:HTTPServer and Threads - Basic, probably...

"Craig" <XXXX@XXXXX.COM>writes
Quote
On connect, is there not something that has to be
done with AThread? Surely, I need a list of threads
stored somewhere?
The server is already handling all of that for you automatically. You don't
have to do any of it yourself. Just implement the OnCommandGet event code
and that is all you need.
Quote
I see there is a ThreadMgr that I am not using.
Does that not need to be set?
If you do not set it yourself, the server automatically sets it for you with
a default manager when the server is activated.
Quote
It writes to a Memo a few times too, just to show the
progress of the transaction.
That is not thread-safe if you are accessing the Memo directly. You must
use the thread's Synchronize() method in order to access the Memo in a
thread-safe manner.
Quote
Nowehere in my current code do I mention a Thread, and
I think it has to be done. When a connect with one client,
all works fine. When I connect with two at exactly the same
time, one gives an 'Error 500', which is correct, because
something is blowing out in the code.
Sounds like your code is probably not thread-safe enough.
Quote
procedure TForm1.HTTPServerConnect(AThread: TIdPeerThread);
begin

StatusBar1.SimpleText := 'Server Connected';

end;
That is not thread-safe for the same reason that accessing the Memo directly
is not thread-safe. Every event that has an AThread parameter is being
triggered in the context of a worker thread, not the main VCL thread.
Always use Synchronize() in order to access the GUI from a worker thread
safely.
Quote
And I think that there, a new thread somehow has to b
started to handle the chitchat between my server and the client.
That is already being handled for you automatically.
Gambit