RTC Forums
May 19, 2024, 07:34:08 PM *
Welcome, Guest. Please login or register.

Login with username, password and session length
 
   Home   Help Login Register  
Pages: [1]
  Print  
Author Topic: How Do I Send Multiple HTTP Client Requests?  (Read 5619 times)
DougB
Newbie
*
Posts: 19


« on: August 11, 2014, 10:19:01 PM »

I need to send multiple simultaneous HTTP requests to different servers.

I need to know if the following approach is correct, or if there's another (recommended) way of doing this:

1. For each connection, I create a TRtcHttpClient (Multithreaded = True) and TRtcDataRequest object.  (The documentation says to use TRtcHttpClient.New/Release instead of Create/Free, but the Data Request object has no such methods, so I assume I can use TRtcDataRequest.Create(nil)?

In another thread (HERE), you mentioned this:

>>3) The comment about using New and Release instead of Create and Free are actually relics from the past, when I was thinking about making the RTC SDK compile cross-compatible with Delphi.NET. You can safely ignore them now.

If this is true, then why is it still prominent in the help file?  

2. I create a single set of event handlers, which get assigned to each new connection/data request instance (e.g., RtcHttpClient.OnConnect := TForm1.ClientConnect, RtcHttpDataRequest.OnBeginRequest := TForm1.DataRequestBeginRequest, etc.)

3. When sending the request, I set the ServerAddr & PortAddr for the connection and then call it's corresponding RtcDataRequest.Post() method.

  - Before calling RtcDataRequest.Post(), I assign the data to the connection:

    RtcHttpClient.Info.asString['Data'] := <string data>;

  - In the BeginRequest() event, I attempt to receive the data, but only get an empty/null string in return (I've confirmed that <string data> is not empty):

    Data := Cli.Info.asString['Data'];

What am I doing wrong here?


4. After I process the message and get a response, I want to disconnect the connection, then delete/free the RtcHttpClient & RtcDataRequest instance (or return them to a pool I created for reuse).  At what point is it safe to do this?  In addition, when disposing, can I call RtcDataRequest.Free and then RtcHttpClient.Release?   The AppClient demo has code that does this, but states:

  // We are releasing all connection components.
  // This will free them from memory,
  // without calling "OnDisconnect".
  // This is something that you should NOT do
  // from a normal client application.

So I need to know the correct way of disposing of them or knowing when they are no longer in use so I can return them to the pool.

5. On another note, I understand the concept of a Job, but didn't find any examples that used them.  If I post a job to a connection, how does the job actually send data on the connection?

Thanks,
Doug
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #1 on: August 12, 2014, 12:17:22 AM »

1. Nothing bad will happen if you use New instead of Create and Release instead of Free, so there is no reason to remove their description from documetation. Their use just isn't necessary anymore.

2. OK.

3. Do NOT use the "Info" object directly on the TRtcHttpClient component. Instead, use the "RtcHttpClient.Request.Info" object when you want to pass any data to the request/response processing events. You can access your data through the TRtcDataClient(Sender).Request.Info object inside the events.

4. It is safe to destroy the TRtcHttpClient component when the connection is no longer active. And that should be the case after calling "DisconnectNow" on the component. You can use the Free method on all RTC components to destroy them, just like you would on any other Delphi component, as long as the component is NOT being referenced or in use someplace else.

5. RTC Jobs do not send data. Posting a RTC Job means placing your own instance of your own TRtcJob class descendant into the thread job queue. You can store any information you may need for that job inside your own object. Your object's "Run" method will be executed from inside the RTC thread, so you will have access to your objects data there. For more information on RTC Jobs, check the TRtcQuickJob component and the explanation of the TRtcJob class from the rtcThrPool unit. For examples, search "TRtcJob" in the "Lib" folder.

Best Regards,
Danijel Tkalcec
Logged
DougB
Newbie
*
Posts: 19


« Reply #2 on: August 12, 2014, 12:37:34 AM »

4.  So for example, when RtcDataRequest.DataReceived(), I could call:

  Cli.DisconnectNow;

  And then in RtcHttpClient.OnDisconnect(), call:

  Cli.Free;
  DataRequest.Free;

  or FMyPoolOfConnections.Return(Cli, DataRequest);  // both objects are returned to pool (I will encapsulate them into another object to make this simpler)

Correct?

5.  I've looked at the documentation, but I'm still unclear as to the purpose.  Is the point to simply provide a Command pattern that people can use with RTC without having to implement their own threads?  How would a connection be used in this case?

What I'm trying to do is have a list of connections for each server I want to send data to.  In the case where I want to send more data on the same connection to a particular server, where that connection may be in the process of sending/receiving data, I was under the impression that Jobs were the correct way to do this, but haven't found a specific example.

Alternatively, I'll have to maintain my own queue of messages for each connection and call RtcDataRequest.Post() for each new message after RequestDataReceived() is called.  Like this:

procedure TForm1.RtcDataRequest1DataReceived(Sender: TRtcConnection);
var
  Cli: TRtcDataClient absolute Sender;
  Data: string;
begin
  // We want to wait for the whole response ...
  if Cli.Response.Done then
  begin
    Data := Cli.Read;
    <Process returned data>
   // process next request for this connection
   if (HasMoreRequests) then
   begin
     RtcDataRequest..Info['Data'].AsString := GetNextMessageFromQueue();
     RtcDataRequest.Post();
  end
  else
    ConnectionPool.Return(Cli);

end;  

Thanks,
Doug


  
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #3 on: August 12, 2014, 12:56:25 AM »

3. Just found an error in my previous response. It should read "RtcDataRequest.Request.Info" and NOT "RtcHttpClient..."

4. No. You can NOT call DisconnectNow from inside a RTC event of the component you are trying to close the connection for, and ... you can NOT destroy a RTC object from inside an event triggered by that same component either. You need to do both of these things outside of the context of the connection component, or you will end up with Access Violations. For example, you could do this by using the TRtcQuickJob component to execute an asynchronous job from the Main Thread to call DisconnectNow and destroy the components.

5. RTC Jobs are a very low-level concept, used by the RTC internally to implement everything multi-threading related. Based on what you have just described, you do NOT need to work with RTC Jobs directly. You ONLY need a Request Queue, which is what you are using already when you prepare your request with the Request object and place it into the request queue with the Post method.

On a side-note: If you plan on using the RtcDataRequest component inside a multi-threaded environment from inside RTC threads, make sure to set the HyperThreading property of the TRtcDataRequest component to TRUE, or you might end up trying to modify the same Request object from multiple threads at the same time, which is most likely going to have unwanted results.

Best Regards,
Danijel Tkalcec
Logged
DougB
Newbie
*
Posts: 19


« Reply #4 on: August 12, 2014, 01:41:00 AM »

3. Ok, that works.

4. Using my own queue as described, when can I be sure the RtcHttpClient and RtcDataRequest instances are safe to free?  

5. Then what I was doing in my TForm1.RtcDataReqeust1DataReceived() event was correct (other than setting HyperThreading to True)?

6. I've created a sample TRtcJob that seems to work to post events, but I'd rather use my own queue.

Thanks,
Doug
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #5 on: August 12, 2014, 07:48:29 AM »

4. The one place you may NOT destroy a RTC object, is from within an event fired by that same object. Doing it from any other place is fine, provided the object is NOT in use. Since you are using your own internal queue from your own threads, you can call DisconnectNow from there and make sure the component won't be used after that. Then, you can safely release the component and anything liked to it.

5. Yes. When you set the HyperThreading property to TRUE on the TRtcDataRequest component, you can safely use its Request object and call its Post method from anywhere, including an event triggered by its TRtcHttpClient component. That way, you can post new requests from inside RTC events, for example when the last request received a response, or if a request was aborted.

6. There is no need to use a TRtcJob descendant for posting requests into the request queue. Just use the RtcDataRequest.Request object and call the RtcDataRequest1.Post method directly from where you want to post a new request into the queue. By setting the HyperThreading property to TRUE on the RtcDataRequest component, the Request and Post methods are 100% thread-safe and can be used anywhere in your code.

Best Regards,
Danijel Tkalcec
Logged
DougB
Newbie
*
Posts: 19


« Reply #6 on: August 12, 2014, 03:44:16 PM »

4.  Ok, but the challenge is knowing when the connection is no longer in use (i.e., when no other events will be fired), as in the following scenario.

I've implemented a QuickJob to call RtcHttpClient.DisconnectNow(), which then posts another QuickJob to free the component:

  a) Client sends request to server
  b) Server sends response
  c) The connection times out automatically
  d) The OnDisconnect event is fired, which in turn posts a job to free the connection.
  e) The job is processed by another thread in the rtc thread pool and the connection is freed.
  f) The OnConnectionLost event gets fired, which passes the connection instance as a parameter, but it's now invalid because it was freed in e)

After looking at the code, I can see that TRtcSocketClientProvider.wsOnSessionClosed() calls TriggerConnectionLost if the connection was 'lost'.  The problem is that TriggerConnectionLost() calls TriggerDisconnect(), which happens in d), and results in e).  Then f) is called, which is the problem.

I've noticed that some events can cause several events to fire, even potentially a data received/done event could be called from a connection lost/disconnect event.

So I need something like an OnConnectionNoLongerInUse() event, so I definitively know when to free the connection and no more events will be called.  Is there such an event or a way to implement such functionality?

Thanks,
Doug
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #7 on: August 12, 2014, 05:51:47 PM »

1. Calling DisconnectNow(TRUE) will skip any pending request from the request queue, close the connection and wait for the component to become idle. Unless you are using the component from some other thread which could post a new request to the request queue, there is nothing else you need to do before you can destroy the component.

2. If you only want to post a single request, wait for a response and then close the connection and destroying the component, I recommend you to use the OnResponseAbort, OnResponseDone and OnResponseReject events for posting the job which would call DisconnectNow and destroy the component, isntead of using the OnDisconnect event. Only one of these three events will be called for each request/respone chain, telling you that your last request was either rejected (manually in your code), aborted (connection problems?) or done (complete response received). I can NOT recommend using connection-related events at all for your purpose, because these events are API-specific and the TRtcHttpClient component can work with 4 different APIs.

The default connection provider of the TRtcHttpClient component is Async WinSock, which uses Asynchronous communication by utilizing Windows Messages. But if your Clients need to work through Proxy Servers, you will need to use the WinInet API (useProxy=TRUE) or WinHTTP API (useWinHTTP=True) instead. And these connection providers do not even have a concept of a connection, but work with requests and responses instead. And then, the OnDisconnect and OnConnectLost events won't even get fired, but OnResponseDone, OnResponseAbort and OnResponseReject events will (depending on the outcome of the request/response chain).

Best Regards,
Danijel Tkalcec
Logged
DougB
Newbie
*
Posts: 19


« Reply #8 on: August 12, 2014, 06:16:39 PM »

1.  That's fine, when I want to force a disconnect.  However, there is still a problem when the connection remains open and a timeout occurs.  In this scenario, OnResponseDone() is called, but I leave the connection open for a period of time (C.TimeoutsOfAPI.ConnectTimeout := 5 in this test) in case there is more data in the request queue to send.  

If no more requests are sent, then the component disconnects.  At this point, I need to destroy the connection.  Hwever, I only see the OnDisconnect() and OnConnectionLost() events get called in this situation.  So it seems I can't just rely on OnResponseAbort, OnResponseDone and OnResponseReject...


Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #9 on: August 12, 2014, 06:52:28 PM »

The scenario you have explained previously was clear. You post a request to the request queue, wait for a response or a disconnect, then close the connection and destroy the components. In that scenario, you will only get one OnResponseDone, OnResponseAbort or OnResponseReject event, after which you can post a quick job from to close the connection and destroy the components. If you are re-using the component, or if you have posted multiple requests into the request queue, use the RequestCount property to check if there are more requests in the queue waiting. If there are, you will get at least one of "OnResponse..." event when that request gets rejected, aborted or done. And so on.

Best Regards,
Danijel Tkalcec
Logged
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.21 | SMF © 2015, Simple Machines Valid XHTML 1.0! Valid CSS!
Page created in 0.029 seconds with 16 queries.